상세 컨텐츠

본문 제목

[딥러닝 입문] AI 개론 1차시 - 딥러닝에 필요한 파이썬 개념 완전 정복

컴퓨터 과학(Computer Science)/인공지능(AI)

by Director J 2026. 5. 18. 23:50

본문

머신러닝과 딥러닝의 차이
e.g. IBM - 자동 인코더

딥러닝 학습 사이클의 큰 그림부터 PyTorch 코드 패턴까지, 입문자가 꼭 알아야 할 핵심을 담았습니다.

목차

  1. 딥러닝 학습 사이클 이해
  2. 손실(Loss)과 역전파
  3. 옵티마이저와 학습률
  4. 컨테이너와 복사 문제
  5. 합성 함수로 본 딥러닝
  6. 미분과 PyTorch Autograd
  7. 클래스(OOP)와 PyTorch 모델 구조
  8. 핵심 개념 Q&A

1. 딥러닝 학습 사이클 이해

인간의 학습 vs 컴퓨터의 학습

딥러닝의 학습 과정은 사실 인간이 공부하는 방식과 똑같습니다.

단계 인간 학생 컴퓨터 (딥러닝)
1 문제집 풀기 순전파 (Forward) - 데이터 보고 예측
2 채점하기 손실 계산 (Loss) - 정답과 비교
3 피드백 확인 오차 측정 - 얼마나 틀렸는지 수치화
4 다시 풀기 역전파/업데이트 - 파라미터 수정

핵심 인사이트

"우리가 시험장에서 문제를 푸는 것도 예측입니다.
ChatGPT의 답변도 마찬가지로 확률값일 뿐, 절대적인 사실이 아닙니다."

 

딥러닝은 이런 예측을 반복적으로 개선하는 알고리즘입니다. 그래서 "정답을 맞춘다"가 아니라 "정답에 가까워진다"가 더 정확한 표현입니다.

PyTorch 학습 5단계 (필수 암기)

optimizer.zero_grad()              # 1. 기울기 초기화
output = model(input)              # 2. 순전파
loss = criterion(output, target)   # 3. 손실 계산
loss.backward()                    # 4. 역전파
optimizer.step()                   # 5. 파라미터 갱신

 

평가 단계에서는 기울기 계산이 불필요하므로 다음 패턴을 사용합니다.

model.eval()
with torch.no_grad():
    output = model(test_input)

2. 손실(Loss)과 역전파

손실(Loss)이란?

손실은 "내 예측이 정답에서 얼마나 멀리 떨어져 있는가"를 숫자로 표현한 값입니다.

정답(5) - 예측(4) = 손실(1)

학습의 목표는 단 하나, 이 손실을 0에 가깝게 만드는 것입니다.

MSE(평균 제곱 오차) 직접 구현하기

가장 기본적인 손실 함수인 MSE를 PyTorch로 구현해봅시다.

import torch

def mse(Yp, Y):
    loss = ((Yp - Y) ** 2).mean()
    return loss

# 테스트
Yp = torch.tensor([1.0, 2.5, 3.8])
Y = torch.tensor([1.2, 2.0, 4.0])
print(f"MSE: {mse(Yp, Y):.4f}")  # 0.1100

 

계산 과정:

  1. 차이: [-0.2, 0.5, -0.2]
  2. 제곱: [0.04, 0.25, 0.04]
  3. 평균: 0.33 / 3 = 0.11

텐서를 사용하면 for 루프 없이 한 줄로 끝납니다. 이것이 NumPy/PyTorch의 강력함입니다.

역전파(Backpropagation)

역전파는 손실에서 시작해 거꾸로 거슬러 올라가며 각 파라미터가 오차에 얼마나 기여했는지 계산하는 과정입니다.

[순전파] x → f₁ → f₂ → ... → fₗ → y (예측)
                                  ↓
                            loss 계산
                                  ↓
[역전파] x ← f₁ ← f₂ ← ... ← fₗ ← loss
        (기울기를 거꾸로 전파)

 

PyTorch에서는 단 한 줄로 끝납니다.

loss.backward()  # 모든 파라미터의 .grad에 미분값 자동 저장

3. 옵티마이저와 학습률

파라미터 업데이트 공식

새 파라미터 = 현재 파라미터 - (학습률 × 기울기)
W_new = W - (lr × grad)

왜 빼는가? 기울기는 손실이 증가하는 방향이므로, 그 반대 방향으로 가야 손실이 줄어듭니다.

학습률(Learning Rate)의 중요성

학습률이 클 때 학습률이 작을 때
최적점을 지나쳐 발산 너무 느려서 도달 못 함
고속도로에서 출구를 놓침 영원히 도착 못 함

수동 업데이트 vs 옵티마이저

수동 방식 (개념 이해용):

loss.backward()

with torch.no_grad():
    W -= lr * W.grad
    B -= lr * B.grad

W.grad.zero_()
B.grad.zero_()

 

옵티마이저 방식 (실무):

optimizer = optim.SGD([W, B], lr=0.001)

# 학습 루프
loss.backward()
optimizer.step()         # 업데이트
optimizer.zero_grad()    # 초기화

모델 파라미터가 수백만 개일 때 수동 업데이트는 불가능합니다. 옵티마이저는 이를 한 줄로 해결해줍니다.

주요 옵티마이저 종류

옵티마이저 특징
SGD 가장 기본
Adam 실무에서 가장 많이 사용
AdamW Transformer 등에서 사용

처음엔 Adam을 쓰면 대부분 무난합니다.


4. 컨테이너와 복사 문제

얕은 복사의 함정

import numpy as np

x = np.array([5, 7, 9])
y = x          # 참조만 복사!
x[0] = 100
print(y)       # [100, 7, 9] - y도 함께 변함!

 

y = x는 값을 복사하는 게 아니라 같은 메모리 주소를 가리키게 만듭니다.

깊은 복사로 안전하게

x = np.array([5, 7, 9])
y = x.copy()   # 새 메모리에 값 복사
x[0] = 100
print(y)       # [5, 7, 9] - 원본 유지!

왜 중요한가?

데이터 전처리 과정에서 원본을 훼손하면 재현 불가능한 버그가 발생합니다.

원본 데이터는 무조건 사수해야 합니다. 비교를 위해서라도 따로 보관하세요.


5. 합성 함수로 본 딥러닝

딥러닝 = 함수의 함수의 함수...

x → [Layer1] → [Layer2] → ... → [LayerL] → y

수식으로 표현하면 다음과 같습니다.

파이썬으로 표현하기

def f1(x): return x ** 2     # 제곱
def f2(x): return 2 * x      # 2배
def f3(x): return x + 2      # +2 더하기

x = 3
y = f3(f2(f1(x)))  # 3 → 9 → 18 → 20
print(y)  # 20

이게 바로 순전파(Forward Propagation)의 본질입니다. 입력이 여러 함수를 거치며 변형되어 최종 출력이 나오는 과정이죠.

왜 이렇게 만들었는가?

한 번에 복잡한 함수를 만들기는 어렵습니다. 대신 단순한 레이어를 쌓아서 복잡한 표현력을 만들어내는 것이 딥러닝의 핵심 전략입니다.


6. 미분과 PyTorch Autograd

미분이 왜 필요한가?

미분은 학습의 나침반입니다. "어느 방향으로 가야 손실이 줄어드는가?"를 알려주죠.

        가중치 ⬆
       📍 (현재 위치)
      ╱
     ╱  ← 미분값 양수 → 반대로(왼쪽) 가야 손실 감소
    ╱
   🎯 (최적점)

수치 미분 (중앙 차분)

def fdiff(f):
    def diff(x):
        h = 1e-6
        return (f(x+h) - f(x-h)) / (2*h)
    return diff

f = lambda x: 2*x**2 + 2
f_prime = fdiff(f)
print(f_prime(3.0))  # 12.0000...

PyTorch Autograd (실무)

수치 미분을 직접 구현할 필요 없이, PyTorch가 자동으로 해줍니다.

import torch

x = torch.tensor(3.0, requires_grad=True)  # 추적 ON
y = 2 * x**2 + 2
y.backward()       # 자동 미분
print(x.grad)      # tensor(12.)

 

핵심 코드: requires_grad=Trues를 절대 빠뜨리지 마세요.

경사 하강법 1회 반복 전체 코드

import torch

# 데이터
X = torch.tensor([-5., 5., 0., 2., -2.])
Y = torch.tensor([-6.7, 10.3, -3.3, 5.0, -5.3])

# 파라미터
W = torch.tensor(1.0, requires_grad=True)
B = torch.tensor(1.0, requires_grad=True)
lr = 0.001

# 예측 & 손실
def pred(X): return W * X + B
def mse(Yp, Y): return ((Yp - Y)**2).mean()

# 1회 반복
Yp = pred(X)
loss = mse(Yp, Y)
loss.backward()

with torch.no_grad():
    W -= lr * W.grad
    B -= lr * B.grad

W.grad.zero_()
B.grad.zero_()

7. 클래스(OOP)와 PyTorch 모델 구조

객체지향 4대 요소

요소 의미 비유
Class (클래스) 설계도 붕어빵 틀
Instance (인스턴스) 실체 실제 붕어빵
Attribute (속성) 데이터/상태 self.weights
Method (메서드) 동작/함수 forward()

Point 클래스 예제

class Point:
    def __init__(self, x, y):    # 생성자 (재료 준비)
        self.x = x
        self.y = y

    def move(self, dx, dy):       # 메서드
        self.x += dx
        self.y += dy

    def coord(self):
        return (self.x, self.y)

p = Point(2, 3)
p.move(1, -1)
print(p.coord())  # (3, 2)

__call__ 매직 메서드의 비밀

class H:
    def __call__(self, x):
        return 2*x + 2

h = H()
print(h(3))  # 8 - 인스턴스를 함수처럼 호출!

PyTorch에서 model.forward(x)가 아닌 model(x)로 쓰는 이유가 바로 이 __call__ 덕분입니다.

PyTorch 모델 표준 구조

import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()        # 부모 클래스 초기화 (필수!)
        self.layer = nn.Linear(10, 1)

    def forward(self, x):
        return self.layer(x)

# 사용
model = MyModel()
output = model(input)  # forward 자동 호출

클래스를 모르면 PyTorch 코드를 절대 읽을 수 없습니다. 모든 LLM, 로봇 코드가 이 구조 위에서 동작합니다.


8. 핵심 개념 Q&A

Q1. requires_grad=True는 정확히 뭘 하나요?

"이 텐서의 변화를 추적해줘"라고 PyTorch에게 알려주는 옵션입니다.

  • 학습 대상 파라미터 (W, b) → True
  • 단순 입력 데이터False (기본값)

Q2. loss.backward()는 왜 loss에서 호출하나요?

우리가 줄이고 싶은 것이 바로 loss이기 때문입니다. "loss를 줄이려면 각 파라미터를 어디로 옮겨야 해?"를 묻는 과정입니다.

loss.backward()  # loss → ... → W로 역추적
print(W.grad)    # 자동으로 미분값이 채워짐

Q3. optimizer.zero_grad()를 왜 매번 호출하나요?

PyTorch는 기울기를 누적하기 때문입니다. 초기화하지 않으면 이전 배치의 기울기가 쌓여서 학습이 폭주합니다.

# 올바른 패턴
for batch in loader:
    optimizer.zero_grad()  # 매번 초기화
    pred = model(batch)
    loss = criterion(pred, target)
    loss.backward()
    optimizer.step()

Q4. model(x)model.forward(x)는 같은 건가요?

결과는 같지만 반드시 model(x)를 써야 합니다.

model(x)__call__을 통해 다음을 추가로 실행합니다:

  • 기울기 추적 설정
  • 학습/평가 모드 확인
  • 디버깅 훅 실행

forward()만 직접 부르면 이런 기능이 모두 무시됩니다.

Q5. model.eval()torch.no_grad()는 같은 건가요?

다릅니다. 둘 다 평가 시 사용하지만 역할이 다릅니다.

구분 model.eval() torch.no_grad()
역할 레이어 동작 변경 기울기 계산 OFF
영향 Dropout, BatchNorm 메모리/속도

 

평가 시엔 함께 사용합니다.

model.eval()
with torch.no_grad():
    output = model(test_input)

Q6. super().__init__()은 왜 쓰나요?

부모 클래스(nn.Module)의 초기화 코드를 실행하기 위해서입니다. 이걸 안 하면 파라미터 등록, 훅 설정 등이 작동하지 않아 모델이 제대로 동작하지 않습니다.

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()  # 무조건 첫 줄!
        ...

Q7. 미분이 왜 학습 방향인가요?

미분값(기울기)은 "여기서 어느 쪽이 오르막인지"를 알려주는 신호입니다.

공식 W_new = W - lr × grad를 보면:

  • 기울기 양수면 → 빼서 → 왼쪽으로 이동
  • 기울기 음수면 → 빼서 → 오른쪽으로 이동

결국 항상 내리막길로 이동하게 됩니다. 그래서 손실이 점점 작아지는 거죠.


마무리: 학습 사이클 한 장으로 정리

[1] 데이터 입력 (Input)
        ↓
[2] 순전파 (Forward)
    output = model(input)
        ↓
[3] 손실 계산 (Loss)
    loss = criterion(output, target)
        ↓
[4] 역전파 (Backward)
    loss.backward()
        ↓
[5] 파라미터 업데이트
    optimizer.step()
        ↓
[6] 기울기 초기화
    optimizer.zero_grad()
        ↓
    (반복)

이 사이클을 수백, 수천 번 반복하면서 모델은 점점 정답에 가까워집니다.


🤔 [1] - [6]까지 한 글자도 빠짐없이 외워야 하나요?

결론부터: 아니요! 😌

암기는 필요 없습니다. 흐름과 의미만 이해하면 됩니다.

 

✅ 진짜 외워야 할 것 (이건 자동으로 외워짐)

핵심 5단계 흐름

순전파 → 손실 → 역전파 → 업데이트 → 초기화
(Forward → Loss → Backward → Step → Zero)

이게 끝입니다. 함수 이름은 코드 짜다 보면 자연스럽게 손에 익어요.


체크리스트

  • 순전파/역전파의 차이를 설명할 수 있다
  • PyTorch 학습 5단계를 외울 수 있다
  • requires_grad=True의 역할을 안다
  • 얕은 복사가 왜 위험한지 안다
  • 클래스의 4대 요소를 안다
  • __call__ 메서드의 역할을 안다
  • 학습률이 크거나 작을 때의 문제를 안다

모든 항목에 체크할 수 있다면 1차시 기초는 완벽합니다.


다음 차시 예고: NumPy와 PyTorch 텐서 심화. 4차원 텐서가 등장합니다.

 

반응형