Guider/AI/AiDevGuide0005
AI#05

AiDevGuide0005

딥러닝 완전 정복

AI DEV GUIDE SERIES

딥러닝 완전 정복 A-Z

신경망 · CNN · RNN · Transformer · TensorFlow · PyTorch 현업 실전

비전공자도 3개월 안에 딥러닝 현업 투입 — 개념부터 배포까지 한 번에!

대상: ML 기초 완료자기간: 4~6주난이도: ★★★★☆Guide 5/7

AI Dev Guide 시리즈 전체 흐름

단계 가이드 핵심 주제 상태
01 AiDevGuide0001 AI 개념 완전 정복 완료
02 AiDevGuide0002 Python 기초 완전 정복 완료
03 AiDevGuide0003 데이터 다루기 (NumPy·Pandas·Matplotlib) 완료
04 AiDevGuide0004 머신러닝 완전 정복 (Scikit-learn) 완료
05 AiDevGuide0005 (현재) 딥러닝 완전 정복 (TensorFlow·PyTorch) 학습중
06 AiDevGuide0006 생성형 AI (OpenAI API·LangChain·RAG) 예정
07 AiDevGuide0007 실전 프로젝트 (HuggingFace·FastAPI·Docker) 예정

전체 학습 목차 (10 Chapters)

Ch. 챕터명 핵심 내용 난이도
01 딥러닝 개요 & 신경망 기초 퍼셉트론, 활성화 함수, 역전파, 경사하강법 ★★☆☆☆
02 TensorFlow / Keras 완전 정복 Sequential/Functional API, 콜백, 모델 저장 ★★★☆☆
03 PyTorch 완전 정복 Tensor, Autograd, nn.Module, DataLoader ★★★☆☆
04 CNN — 이미지 분류 완전 정복 Conv2D, Pooling, ResNet, VGG, EfficientNet ★★★★☆
05 RNN / LSTM / GRU — 시계열 & 텍스트 순환 신경망, 장기 의존성, 텍스트 분류 ★★★★☆
06 Transformer & Attention 메커니즘 Self-Attention, Multi-Head, BERT, GPT 구조 ★★★★★
07 Transfer Learning & Fine-tuning 사전학습 모델 활용, Feature Extraction, PEFT ★★★★☆
08 정규화 & 최적화 심화 Dropout, BatchNorm, Adam, 학습률 스케줄러 ★★★★☆
09 실전 프로젝트 (MNIST·CIFAR-10·감성분석) E2E 파이프라인, GPU 활용, 모델 배포 ★★★★★
10 현업 면접 Q&A TOP 10 딥러닝 실전 면접 핵심 질문 & 완벽 답변 ★★★★★

Ch 01. 딥러닝 개요 & 신경망 기초

퍼셉트론부터 역전파까지 — 딥러닝의 작동 원리를 완전히 이해한다

1-1. 딥러닝이란 무엇인가?

딥러닝(Deep Learning)은 인간 뇌의 신경망 구조를 모방한 머신러닝의 한 분야입니다. "Deep"는 여러 층(Layer)을 쌓는다는 의미이며, 각 층이 데이터의 점점 더 추상적인 특징을 학습합니다.

구분 머신러닝 딥러닝
특징 추출 수동 (사람이 직접) 자동 (모델이 학습)
필요 데이터 소량~중간 대용량 (GB~TB)
해석 가능성 비교적 높음 블랙박스 (낮음)
연산 자원 CPU 충분 GPU/TPU 필요
성능 (이미지/음성) 한계 존재 인간 수준 또는 초과

1-2. 퍼셉트론(Perceptron) — 신경망의 기본 단위

퍼셉트론은 생물학적 뉴런을 수학적으로 모델링한 것입니다.

# 퍼셉트론 수식
# 입력: x1, x2, ... xn
# 가중치: w1, w2, ... wn
# 편향: b
# 출력: y = activation(w1*x1 + w2*x2 + ... + wn*xn + b)

import numpy as np

class Perceptron:
    def __init__(self, n_inputs, lr=0.01):
        self.w = np.random.randn(n_inputs) * 0.01  # 가중치 초기화
        self.b = 0.0                                 # 편향 초기화
        self.lr = lr                                 # 학습률

    def activate(self, z):
        return 1 if z >= 0 else 0  # 계단 함수 (Step Function)

    def predict(self, x):
        z = np.dot(self.w, x) + self.b  # 선형 결합
        return self.activate(z)

    def fit(self, X, y, epochs=10):
        for epoch in range(epochs):
            for xi, yi in zip(X, y):
                y_pred = self.predict(xi)
                error = yi - y_pred           # 오차 계산
                self.w += self.lr * error * xi  # 가중치 업데이트
                self.b += self.lr * error       # 편향 업데이트

1-3. 활성화 함수(Activation Function) 완전 정복

함수 수식 범위 주요 용도 단점
ReLU max(0, x) [0, ∞) 은닉층 기본값 Dying ReLU
Leaky ReLU x>=0: x, x<0: 0.01x (-∞, ∞) ReLU 개선 alpha 튜닝 필요
Sigmoid 1/(1+e^-x) (0, 1) 이진 분류 출력 기울기 소실
Tanh (e^x-e^-x)/(e^x+e^-x) (-1, 1) RNN 내부 기울기 소실
Softmax e^xi / sum(e^xj) (0,1) 합=1 다중 분류 출력 수치 불안정

1-4. 역전파(Backpropagation) & 경사하강법

역전파는 출력 오차를 역방향으로 전파하여 각 가중치의 기울기(gradient)를 계산하는 알고리즘입니다.

# 경사하강법 종류 비교
# 1. BGD (Batch Gradient Descent): 전체 데이터 사용 → 안정적이나 느림
# 2. SGD (Stochastic): 1개씩 업데이트 → 빠르나 불안정
# 3. Mini-batch: 배치 단위 업데이트 → 실제 현업 표준

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_deriv(x):
    s = sigmoid(x)
    return s * (1 - s)  # 시그모이드 미분

# 2층 신경망 순전파 + 역전파
class SimpleNN:
    def __init__(self):
        np.random.seed(42)
        self.W1 = np.random.randn(2, 4) * 0.01  # 입력(2) -> 은닉(4)
        self.b1 = np.zeros((1, 4))
        self.W2 = np.random.randn(4, 1) * 0.01  # 은닉(4) -> 출력(1)
        self.b2 = np.zeros((1, 1))

    def forward(self, X):
        self.z1 = X @ self.W1 + self.b1          # 1층 선형 결합
        self.a1 = sigmoid(self.z1)                # 1층 활성화
        self.z2 = self.a1 @ self.W2 + self.b2    # 2층 선형 결합
        self.a2 = sigmoid(self.z2)                # 출력층
        return self.a2

    def backward(self, X, y, lr=0.01):
        m = X.shape[0]
        dL_da2 = -(y - self.a2)                   # 손실 기울기
        dL_dz2 = dL_da2 * sigmoid_deriv(self.z2) # 역전파: 출력층
        dL_dW2 = self.a1.T @ dL_dz2 / m
        dL_db2 = dL_dz2.mean(axis=0, keepdims=True)

        dL_da1 = dL_dz2 @ self.W2.T
        dL_dz1 = dL_da1 * sigmoid_deriv(self.z1) # 역전파: 은닉층
        dL_dW1 = X.T @ dL_dz1 / m
        dL_db1 = dL_dz1.mean(axis=0, keepdims=True)

        # 가중치 업데이트
        self.W2 -= lr * dL_dW2
        self.b2 -= lr * dL_db2
        self.W1 -= lr * dL_dW1
        self.b1 -= lr * dL_db1

1-5. 손실 함수(Loss Function) 종류

손실 함수 수식 사용 상황
MSE mean((y - y_hat)^2) 회귀 문제
Binary Crossentropy -[y*log(p) + (1-y)*log(1-p)] 이진 분류
Categorical Crossentropy -sum(y * log(p)) 다중 분류 (원핫)

Ch 02. TensorFlow / Keras 완전 정복

구글이 만든 딥러닝 프레임워크 — Sequential부터 커스텀 레이어까지

2-1. TensorFlow 설치 및 환경 설정

# TensorFlow 설치
pip install tensorflow  # CPU 버전
pip install tensorflow-gpu  # GPU 버전 (CUDA 필요)

# Google Colab: 이미 설치되어 있음!
# GPU 확인
import tensorflow as tf
print("TF 버전:", tf.__version__)
print("GPU 사용 가능:", tf.config.list_physical_devices("GPU"))

# GPU 메모리 설정 (OOM 방지)
gpus = tf.config.experimental.list_physical_devices("GPU")
if gpus:
    for gpu in gpus:
        tf.config.experimental.set_memory_growth(gpu, True)  # 필요한 만큼만 사용
print("GPU 설정 완료")

2-2. Sequential API — 가장 빠른 시작

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np

# === Sequential API: 층을 순서대로 쌓기 ===
model = keras.Sequential([
    # 입력층 + 첫 번째 은닉층
    layers.Dense(128, activation="relu", input_shape=(784,)),
    layers.Dropout(0.3),           # 과적합 방지

    # 두 번째 은닉층
    layers.Dense(64, activation="relu"),
    layers.BatchNormalization(),   # 배치 정규화
    layers.Dropout(0.3),

    # 출력층 (10개 클래스)
    layers.Dense(10, activation="softmax")
])

# 모델 요약
model.summary()

# 컴파일: 손실함수, 옵티마이저, 평가지표 지정
model.compile(
    optimizer=keras.optimizers.Adam(learning_rate=0.001),
    loss="sparse_categorical_crossentropy",  # 정수 레이블
    metrics=["accuracy"]
)

# MNIST 데이터로 학습
(X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()
X_train = X_train.reshape(-1, 784) / 255.0  # 정규화
X_test  = X_test.reshape(-1, 784)  / 255.0

history = model.fit(
    X_train, y_train,
    epochs=10,
    batch_size=256,
    validation_split=0.2,
    verbose=1
)

# 평가
loss, acc = model.evaluate(X_test, y_test, verbose=0)
print(f"테스트 정확도: {acc:.4f}")

2-3. Functional API — 복잡한 모델 구성

# Functional API: 다중 입력/출력, 잔차 연결 등 복잡한 구조 가능
inputs = keras.Input(shape=(784,))
x = layers.Dense(256, activation="relu")(inputs)
x = layers.Dropout(0.4)(x)
x = layers.Dense(128, activation="relu")(x)
skip = layers.Dense(128, activation="relu")(inputs)  # 잔차 연결
x = layers.Add()([x, skip])                           # 더하기
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10, activation="softmax")(x)

model = keras.Model(inputs=inputs, outputs=outputs)
model.summary()

2-4. 콜백(Callbacks) — 학습 제어의 핵심

# 현업에서 필수 콜백 모음
callbacks = [
    # 1. EarlyStopping: 검증 손실이 개선 없으면 조기 종료
    keras.callbacks.EarlyStopping(
        monitor="val_loss",
        patience=5,          # 5 epoch 동안 개선 없으면 중단
        restore_best_weights=True  # 최고 성능 가중치 복원
    ),

    # 2. ModelCheckpoint: 최고 모델 저장
    keras.callbacks.ModelCheckpoint(
        filepath="best_model.keras",
        monitor="val_accuracy",
        save_best_only=True
    ),

    # 3. ReduceLROnPlateau: 학습률 자동 감소
    keras.callbacks.ReduceLROnPlateau(
        monitor="val_loss",
        factor=0.5,    # 학습률을 절반으로
        patience=3,
        min_lr=1e-7
    ),

    # 4. TensorBoard: 학습 시각화
    keras.callbacks.TensorBoard(log_dir="./logs")
]

history = model.fit(X_train, y_train,
    epochs=100, batch_size=256,
    validation_split=0.2,
    callbacks=callbacks
)

2-5. 모델 저장 & 로드

# === 모델 저장 방법 3가지 ===

# 1. 전체 모델 저장 (권장)
model.save("my_model.keras")          # Keras 형식
model.save("my_model.h5")             # HDF5 형식 (레거시)

# 2. 가중치만 저장
model.save_weights("weights.h5")
model.load_weights("weights.h5")      # 가중치만 로드

# 3. SavedModel 형식 (TF Serving 배포용)
model.save("saved_model_dir")         # 디렉터리로 저장

# 로드
loaded_model = keras.models.load_model("my_model.keras")
predictions = loaded_model.predict(X_test[:5])
print("예측 클래스:", predictions.argmax(axis=1))

# TF Lite 변환 (모바일/엣지 배포용)
converter = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir")
tflite_model = converter.convert()
with open("model.tflite", "wb") as f:
    f.write(tflite_model)
print("TFLite 변환 완료!")

Ch 03. PyTorch 완전 정복

연구·현업 모두 최애 프레임워크 — Tensor부터 커스텀 모델까지

3-1. PyTorch vs TensorFlow 비교

항목 PyTorch TensorFlow/Keras
철학 Dynamic Graph (즉시 실행) Static Graph (초기) + Eager(2.x)
디버깅 Python 디버거 그대로 사용 tf.function 주의 필요
연구 인기도 압도적 (논문 70%+) 산업 분야 강세
HuggingFace 기본 지원 지원 (일부 제한)
모바일 배포 TorchScript, ONNX TF Lite 강력

3-2. Tensor 기초 완전 정복

import torch
import torch.nn as nn

# === Tensor 생성 ===
t1 = torch.tensor([[1.0, 2.0], [3.0, 4.0]])  # 리스트로
t2 = torch.zeros(3, 4)          # 0으로 채우기
t3 = torch.ones(3, 4)           # 1로 채우기
t4 = torch.randn(3, 4)          # 정규분포 랜덤
t5 = torch.arange(0, 10, 2)     # range처럼
t6 = torch.linspace(0, 1, 5)    # 균등 간격

# NumPy 변환
import numpy as np
arr = np.array([1, 2, 3])
t = torch.from_numpy(arr)       # NumPy → Tensor
arr2 = t.numpy()                # Tensor → NumPy (CPU만 가능)

# GPU 이동
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
t = t4.to(device)               # GPU로 이동

# Tensor 속성
print(t4.shape)    # torch.Size([3, 4])
print(t4.dtype)    # torch.float32
print(t4.device)   # cpu or cuda:0

# 형태 변환
t = torch.randn(2, 3, 4)
print(t.reshape(6, 4))    # reshape
print(t.view(24))         # view (메모리 연속)
print(t.permute(2, 0, 1)) # 축 순서 변경
print(t.squeeze())        # 크기 1인 차원 제거
print(t.unsqueeze(0))     # 차원 추가

3-3. Autograd — 자동 미분 엔진

# requires_grad=True: 이 텐서의 기울기를 추적
x = torch.tensor([2.0, 3.0], requires_grad=True)
y = x[0]**2 + 2*x[1]   # y = x0^2 + 2*x1
y.backward()             # 역전파
print(x.grad)            # [dy/dx0, dy/dx1] = [4.0, 2.0]

# 기울기 초기화 (배치마다 필수!)
optimizer.zero_grad()    # 항상 backward 전에!

# no_grad: 평가 시 메모리 절약
with torch.no_grad():
    output = model(test_input)  # 기울기 계산 안 함

3-4. nn.Module — 커스텀 모델 만들기

import torch.nn as nn
import torch.nn.functional as F

class MLP(nn.Module):
    """다층 퍼셉트론 — PyTorch 커스텀 모델 기본 틀"""
    def __init__(self, input_dim, hidden_dim, output_dim, dropout=0.3):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.BatchNorm1d(hidden_dim),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim, hidden_dim // 2),
            nn.BatchNorm1d(hidden_dim // 2),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim // 2, output_dim)
        )

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

model = MLP(784, 256, 10).to(device)
print(model)

3-5. 완전한 학습 루프 (PyTorch 표준 패턴)

from torch.utils.data import TensorDataset, DataLoader

# DataLoader 구성
X_tensor = torch.FloatTensor(X_train)
y_tensor = torch.LongTensor(y_train)
dataset = TensorDataset(X_tensor, y_tensor)
loader  = DataLoader(dataset, batch_size=256, shuffle=True, num_workers=4)

# 옵티마이저 & 손실 함수
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10)
criterion = nn.CrossEntropyLoss()

best_val_loss = float("inf")

for epoch in range(50):
    # === 학습 모드 ===
    model.train()
    train_loss = 0
    for X_batch, y_batch in loader:
        X_batch, y_batch = X_batch.to(device), y_batch.to(device)
        optimizer.zero_grad()           # 1. 기울기 초기화
        outputs = model(X_batch)        # 2. 순전파
        loss = criterion(outputs, y_batch)  # 3. 손실 계산
        loss.backward()                 # 4. 역전파
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)  # 기울기 클리핑
        optimizer.step()                # 5. 가중치 업데이트
        train_loss += loss.item()

    # === 평가 모드 ===
    model.eval()
    val_loss, correct = 0, 0
    with torch.no_grad():
        for X_batch, y_batch in val_loader:
            X_batch = X_batch.to(device)
            y_batch = y_batch.to(device)
            out = model(X_batch)
            val_loss += criterion(out, y_batch).item()
            correct += (out.argmax(1) == y_batch).sum().item()

    scheduler.step()
    avg_val_loss = val_loss / len(val_loader)

    # 최고 모델 저장
    if avg_val_loss < best_val_loss:
        best_val_loss = avg_val_loss
        torch.save(model.state_dict(), "best_model.pt")

    if (epoch+1) % 5 == 0:
        acc = correct / len(val_dataset)
        print(f"Epoch {epoch+1} | Val Loss: {avg_val_loss:.4f} | Val Acc: {acc:.4f}")

# 최고 모델 로드
model.load_state_dict(torch.load("best_model.pt"))
model.eval()
print("최고 모델 로드 완료!")

Ch 04. CNN — 이미지 분류 완전 정복

컴퓨터가 이미지를 보는 방법 — Conv2D부터 ResNet까지

4-1. CNN 핵심 개념 — 합성곱이란?

CNN(Convolutional Neural Network)은 이미지의 지역적 패턴을 효율적으로 학습합니다.

구성 요소 역할 파라미터
Conv2D 필터로 특징 맵 추출 filters, kernel_size, stride, padding
MaxPooling2D 공간 크기 축소, 주요 특징 보존 pool_size, stride
BatchNorm 레이어 내 정규화, 학습 안정화 momentum, epsilon
Flatten/GAP 특징 맵 → 벡터 변환 -

4-2. CNN 구축 — TensorFlow 버전

import tensorflow as tf
from tensorflow.keras import layers, models

# CIFAR-10: 32x32 컬러 이미지, 10개 클래스
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.cifar10.load_data()
X_train = X_train / 255.0
X_test  = X_test  / 255.0

def build_cnn():
    model = models.Sequential([
        # 블록 1: 32 필터
        layers.Conv2D(32, (3,3), padding="same", input_shape=(32,32,3)),
        layers.BatchNormalization(),
        layers.Activation("relu"),
        layers.Conv2D(32, (3,3), padding="same"),
        layers.BatchNormalization(),
        layers.Activation("relu"),
        layers.MaxPooling2D((2,2)),
        layers.Dropout(0.25),

        # 블록 2: 64 필터
        layers.Conv2D(64, (3,3), padding="same"),
        layers.BatchNormalization(),
        layers.Activation("relu"),
        layers.Conv2D(64, (3,3), padding="same"),
        layers.BatchNormalization(),
        layers.Activation("relu"),
        layers.MaxPooling2D((2,2)),
        layers.Dropout(0.25),

        # 블록 3: 128 필터
        layers.Conv2D(128, (3,3), padding="same"),
        layers.BatchNormalization(),
        layers.Activation("relu"),
        layers.GlobalAveragePooling2D(),  # GAP: Flatten 대신
        layers.Dropout(0.5),

        # 분류층
        layers.Dense(256, activation="relu"),
        layers.Dropout(0.5),
        layers.Dense(10, activation="softmax")
    ])
    return model

model = build_cnn()
model.compile(
    optimizer=tf.keras.optimizers.Adam(1e-3),
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"]
)

# 데이터 증강 (Data Augmentation)
datagen = tf.keras.preprocessing.image.ImageDataGenerator(
    rotation_range=15,
    width_shift_range=0.1,
    height_shift_range=0.1,
    horizontal_flip=True,
    zoom_range=0.1
)
datagen.fit(X_train)

history = model.fit(
    datagen.flow(X_train, y_train, batch_size=64),
    epochs=50,
    validation_data=(X_test, y_test),
    callbacks=[tf.keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)]
)

4-3. ResNet — 잔차 연결로 깊은 네트워크 학습

# PyTorch로 ResNet 잔차 블록 구현
import torch.nn as nn

class ResidualBlock(nn.Module):
    """ResNet 기본 블록 — 잔차 연결로 기울기 소실 해결"""
    def __init__(self, in_ch, out_ch, stride=1):
        super().__init__()
        self.conv1 = nn.Conv2d(in_ch, out_ch, 3, stride, 1, bias=False)
        self.bn1   = nn.BatchNorm2d(out_ch)
        self.conv2 = nn.Conv2d(out_ch, out_ch, 3, 1, 1, bias=False)
        self.bn2   = nn.BatchNorm2d(out_ch)
        self.relu  = nn.ReLU(inplace=True)

        # 차원이 다를 때 shortcut 조정
        self.shortcut = nn.Sequential()
        if stride != 1 or in_ch != out_ch:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_ch, out_ch, 1, stride, bias=False),
                nn.BatchNorm2d(out_ch)
            )

    def forward(self, x):
        identity = self.shortcut(x)      # 잔차(skip) 경로
        out = self.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out = self.relu(out + identity)  # 잔차 연결!
        return out

# 사전학습 ResNet50 활용 (Transfer Learning)
from torchvision import models as tv_models

resnet = tv_models.resnet50(pretrained=True)
for param in resnet.parameters():    # 기존 파라미터 동결
    param.requires_grad = False

# 분류 헤드만 교체
num_features = resnet.fc.in_features
resnet.fc = nn.Sequential(
    nn.Linear(num_features, 256),
    nn.ReLU(),
    nn.Dropout(0.5),
    nn.Linear(256, 10)  # 내 데이터셋 클래스 수
)

4-4. 주요 CNN 아키텍처 비교

모델 연도 파라미터 ImageNet Top-1 특징
VGG16 2014 138M 74.5% 단순하고 이해 쉬움
ResNet50 2015 25M 76.1% 잔차 연결, 표준
EfficientNetB0 2019 5.3M 77.3% 경량 + 고성능
ConvNeXt 2022 28M 82.1% Transformer 아이디어 적용
ViT-B/16 2020 86M 81.8% Transformer 기반 이미지

Ch 05. RNN / LSTM / GRU — 시계열 & 텍스트

순서가 있는 데이터 처리 — 주가 예측, 감성 분석, 기계 번역

5-1. RNN의 핵심 아이디어 — 이전 상태 기억하기

RNN은 시퀀스의 각 타임스텝마다 이전 출력(은닉 상태)을 다음 입력으로 활용합니다.

모델 장기 기억 파라미터 수 주요 용도
기본 RNN X (기울기 소실) 적음 교육 목적
LSTM O (Cell State) 많음 (4배) 텍스트, 시계열
GRU O (간소화) 중간 (3배) 빠른 학습 필요시

5-2. LSTM으로 주가 예측 — 시계열 실전

import numpy as np
import torch
import torch.nn as nn

class LSTMPredictor(nn.Module):
    def __init__(self, input_size=1, hidden_size=64, num_layers=2, dropout=0.2):
        super().__init__()
        self.lstm = nn.LSTM(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            dropout=dropout if num_layers > 1 else 0,
            batch_first=True,   # (batch, seq, feature) 형식
            bidirectional=True  # 양방향 LSTM
        )
        self.fc = nn.Sequential(
            nn.Linear(hidden_size * 2, 32),  # 양방향이므로 *2
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(32, 1)  # 다음 값 예측
        )

    def forward(self, x):
        out, _ = self.lstm(x)   # out: (batch, seq, hidden*2)
        out = out[:, -1, :]    # 마지막 타임스텝만 사용
        return self.fc(out)

# 시계열 데이터 슬라이딩 윈도우 생성
def create_sequences(data, seq_len=60):
    X, y = [], []
    for i in range(len(data) - seq_len):
        X.append(data[i:i+seq_len])         # 60일 입력
        y.append(data[i+seq_len])            # 다음날 예측
    return np.array(X), np.array(y)

# 정규화 (시계열은 MinMaxScaler 주로 사용)
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaled = scaler.fit_transform(prices.reshape(-1,1))
X, y = create_sequences(scaled, seq_len=60)
X = torch.FloatTensor(X)  # (samples, 60, 1)
y = torch.FloatTensor(y)

5-3. LSTM으로 텍스트 감성 분석

# 텍스트 → 숫자 변환 파이프라인
import torch
import torch.nn as nn

class SentimentLSTM(nn.Module):
    def __init__(self, vocab_size, embed_dim=128, hidden_dim=256, n_layers=2, dropout=0.3):
        super().__init__()
        # 임베딩: 단어 → 벡터
        self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
        # LSTM
        self.lstm = nn.LSTM(embed_dim, hidden_dim, n_layers,
                             batch_first=True, dropout=dropout, bidirectional=True)
        # 어텐션 가중치
        self.attention = nn.Linear(hidden_dim * 2, 1)
        # 분류
        self.classifier = nn.Sequential(
            nn.Dropout(dropout),
            nn.Linear(hidden_dim * 2, 64),
            nn.ReLU(),
            nn.Linear(64, 1),
            nn.Sigmoid()  # 이진 분류
        )

    def forward(self, x):
        embedded = self.embedding(x)           # (batch, seq, embed)
        lstm_out, _ = self.lstm(embedded)      # (batch, seq, hidden*2)
        # 어텐션 메커니즘
        attn_weights = torch.softmax(self.attention(lstm_out), dim=1)
        context = (lstm_out * attn_weights).sum(dim=1)  # 가중 평균
        return self.classifier(context)

Ch 06. Transformer & Attention 메커니즘

현대 AI의 핵심 — GPT, BERT, ChatGPT의 기반 구조 완전 이해

6-1. Attention이란? — "중요한 것에 집중"

Attention 메커니즘은 시퀀스의 각 위치가 다른 위치들과의 관련성을 직접 계산합니다. "나는 은행에 갔다"에서 "은행"의 의미가 문맥에 따라 달라지는 문제를 해결합니다.

# Self-Attention 수식
# Attention(Q, K, V) = softmax(QK^T / sqrt(dk)) * V

import torch
import torch.nn as nn
import math

class SelfAttention(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.d_k = d_model // n_heads
        self.n_heads = n_heads
        self.W_q = nn.Linear(d_model, d_model)  # Query
        self.W_k = nn.Linear(d_model, d_model)  # Key
        self.W_v = nn.Linear(d_model, d_model)  # Value
        self.out = nn.Linear(d_model, d_model)

    def forward(self, x, mask=None):
        B, T, C = x.shape
        Q = self.W_q(x).view(B, T, self.n_heads, self.d_k).transpose(1, 2)
        K = self.W_k(x).view(B, T, self.n_heads, self.d_k).transpose(1, 2)
        V = self.W_v(x).view(B, T, self.n_heads, self.d_k).transpose(1, 2)
        # Scaled Dot-Product Attention
        scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
        if mask is not None:
            scores = scores.masked_fill(mask == 0, -1e9)
        attn = torch.softmax(scores, dim=-1)
        out = torch.matmul(attn, V).transpose(1, 2).contiguous()
        out = out.view(B, T, C)
        return self.out(out)

6-2. BERT vs GPT 구조 비교

항목 BERT (Encoder) GPT (Decoder)
Attention 방향 양방향 (좌우 모두) 단방향 (왼쪽만)
사전학습 목적 MLM (마스크 채우기) CLM (다음 토큰 예측)
잘하는 것 분류, NER, QA 텍스트 생성
대표 모델 BERT, RoBERTa, ELECTRA GPT-2/3/4, ChatGPT

6-3. 위치 인코딩(Positional Encoding)

# Transformer는 순서 정보가 없음 → 위치 인코딩으로 해결
import torch
import torch.nn as nn
import math

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000, dropout=0.1):
        super().__init__()
        self.dropout = nn.Dropout(dropout)
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)  # 짝수 차원: sin
        pe[:, 1::2] = torch.cos(position * div_term)  # 홀수 차원: cos
        self.register_buffer("pe", pe.unsqueeze(0))   # (1, max_len, d_model)

    def forward(self, x):
        return self.dropout(x + self.pe[:, :x.size(1)])

Ch 07. Transfer Learning & Fine-tuning

거인의 어깨 위에 서기 — 사전학습 모델로 적은 데이터도 최고 성능

7-1. Transfer Learning 전략 3가지

전략 학습 대상 데이터 크기 적합 상황
Feature Extraction 헤드만 학습 매우 적음 도메인 유사, 데이터 부족
Fine-tuning (일부) 헤드 + 상위 몇 층 중간 도메인 약간 다름
Full Fine-tuning 전체 모델 충분 도메인 많이 다름

7-2. HuggingFace Transformers — 현업 표준 라이브러리

pip install transformers datasets accelerate

# === BERT로 텍스트 분류 Fine-tuning ===
from transformers import (
    AutoTokenizer, AutoModelForSequenceClassification,
    TrainingArguments, Trainer
)
from datasets import load_dataset
import numpy as np

# 1. 모델 & 토크나이저 로드
model_name = "klue/bert-base"  # 한국어 BERT
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
    model_name, num_labels=2
)

# 2. 데이터셋 준비
dataset = load_dataset("nsmc")  # 네이버 영화 리뷰

def tokenize_fn(examples):
    return tokenizer(
        examples["document"],
        truncation=True,
        max_length=128,
        padding="max_length"
    )

tokenized = dataset.map(tokenize_fn, batched=True)

# 3. 학습 설정
args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=32,
    per_device_eval_batch_size=64,
    learning_rate=2e-5,
    weight_decay=0.01,
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    logging_dir="./logs"
)

# 4. 평가 함수
def compute_metrics(pred):
    labels = pred.label_ids
    preds = pred.predictions.argmax(-1)
    acc = (preds == labels).mean()
    return {"accuracy": acc}

# 5. 학습 실행
trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized["train"],
    eval_dataset=tokenized["test"],
    compute_metrics=compute_metrics
)
trainer.train()

# 6. 추론
inputs = tokenizer("이 영화 정말 재미있어요!", return_tensors="pt")
outputs = model(**inputs)
pred = outputs.logits.argmax(-1).item()
print("감성:", "긍정" if pred == 1 else "부정")

7-3. PEFT / LoRA — 경량 파인튜닝

LoRA(Low-Rank Adaptation)는 대형 모델을 극소량의 파라미터만 추가하여 파인튜닝합니다. GPU 메모리 10배 절약!

pip install peft

from peft import LoraConfig, get_peft_model, TaskType

# LoRA 설정
lora_config = LoraConfig(
    task_type=TaskType.SEQ_CLS,
    r=8,                    # 랭크 (작을수록 파라미터 적음)
    lora_alpha=16,          # 스케일 (보통 r*2)
    target_modules=["query", "value"],  # 어떤 층에 적용
    lora_dropout=0.1,
    bias="none"
)

# LoRA 적용
peft_model = get_peft_model(model, lora_config)
peft_model.print_trainable_parameters()
# trainable params: 294,912 || all params: 110,108,160 || trainable%: 0.27%
# ← 전체의 0.27%만 학습! 나머지는 동결

Ch 08. 정규화 & 최적화 심화

모델이 잘 학습되도록 — 과적합 방지부터 학습률 스케줄링까지

8-1. 과적합(Overfitting) 방지 전략

기법 원리 코드
Dropout 학습 중 랜덤하게 뉴런 비활성화 nn.Dropout(p=0.5)
L2 정규화 가중치 크기에 페널티 weight_decay=1e-4
Early Stopping 검증 손실 개선 없으면 중단 EarlyStopping(patience=5)
Data Augmentation 훈련 데이터 인위적 증가 ImageDataGenerator

8-2. 배치 정규화(Batch Normalization)

# BatchNorm: 각 미니배치의 분포를 정규화
# 장점: 학습 안정화, 학습률 높게 쓸 수 있음, 정규화 효과

# Conv 계열: BatchNorm2d
nn.Sequential(
    nn.Conv2d(64, 128, 3, padding=1),
    nn.BatchNorm2d(128),   # Conv 다음에 위치
    nn.ReLU()
)

# Linear 계열: BatchNorm1d
nn.Sequential(
    nn.Linear(256, 128),
    nn.BatchNorm1d(128),
    nn.ReLU()
)

# LayerNorm: Transformer에서 주로 사용
nn.LayerNorm(d_model)

8-3. 옵티마이저 종류와 선택 가이드

옵티마이저 특징 추천 상황
Adam 적응적 학습률, 빠른 수렴 기본값 (대부분 상황)
AdamW Adam + 가중치 감소 수정 Transformer (BERT, GPT)
SGD + momentum 일반화 성능 높음 이미지 분류 최종 학습
Lion Google 2023, 메모리 효율 대형 모델 학습

8-4. 학습률 스케줄링 — 학습 중 학습률 조정

import torch.optim as optim

optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)

# 1. CosineAnnealingLR: 코사인 곡선으로 감소
scheduler1 = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)

# 2. OneCycleLR: 워밍업 + 코사인 감소 (현업 강력 추천)
scheduler2 = optim.lr_scheduler.OneCycleLR(
    optimizer,
    max_lr=1e-3,
    epochs=50,
    steps_per_epoch=len(train_loader)
)

# 3. Warmup + Linear Decay (Transformer 표준)
from transformers import get_linear_schedule_with_warmup
scheduler3 = get_linear_schedule_with_warmup(
    optimizer,
    num_warmup_steps=500,    # 첫 500스텝은 학습률 증가
    num_training_steps=5000  # 이후 선형 감소
)

# 학습 루프에서 사용
for batch in train_loader:
    loss = compute_loss(batch)
    loss.backward()
    optimizer.step()
    scheduler2.step()  # 배치마다 (OneCycleLR)
    optimizer.zero_grad()

Ch 09. 실전 프로젝트 — MNIST · CIFAR-10 · 감성분석

End-to-End 딥러닝 파이프라인 — 데이터 준비부터 모델 배포까지

9-1. 프로젝트 1: MNIST 손글씨 인식 (정확도 99%+ 목표)

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# === 데이터 전처리 ===
transform_train = transforms.Compose([
    transforms.RandomRotation(10),           # 증강: 회전
    transforms.RandomAffine(0, translate=(0.1, 0.1)),  # 이동
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))  # MNIST 통계
])
transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])

train_dataset = datasets.MNIST("./data", train=True, download=True, transform=transform_train)
test_dataset  = datasets.MNIST("./data", train=False, transform=transform_test)
train_loader  = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)
test_loader   = DataLoader(test_dataset,  batch_size=256, shuffle=False)

# === CNN 모델 ===
class MNISTNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(),
            nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(),
            nn.MaxPool2d(2), nn.Dropout2d(0.25),
            nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(),
            nn.MaxPool2d(2), nn.Dropout2d(0.25)
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(128*7*7, 256), nn.ReLU(), nn.Dropout(0.5),
            nn.Linear(256, 10)
        )

    def forward(self, x):
        return self.classifier(self.features(x))

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = MNISTNet().to(device)
optimizer = optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-4)
scheduler = optim.lr_scheduler.OneCycleLR(optimizer, max_lr=1e-3,
                                           epochs=15, steps_per_epoch=len(train_loader))
criterion = nn.CrossEntropyLoss()

# 학습
for epoch in range(15):
    model.train()
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        loss = criterion(model(images), labels)
        loss.backward()
        optimizer.step()
        scheduler.step()

# 최종 평가
model.eval()
correct = total = 0
with torch.no_grad():
    for images, labels in test_loader:
        images = images.to(device)
        preds = model(images).argmax(1).cpu()
        correct += (preds == labels).sum().item()
        total += labels.size(0)
print(f"테스트 정확도: {100*correct/total:.2f}%")  # 목표: 99.4%+

9-2. 프로젝트 2: 한국어 감성 분석 (BERT 파인튜닝)

from transformers import AutoTokenizer, AutoModelForSequenceClassification
from torch.utils.data import Dataset, DataLoader
import pandas as pd

class NSMCDataset(Dataset):
    def __init__(self, df, tokenizer, max_len=128):
        self.texts  = df["document"].tolist()
        self.labels = df["label"].tolist()
        self.tokenizer = tokenizer
        self.max_len   = max_len

    def __len__(self): return len(self.texts)

    def __getitem__(self, idx):
        enc = self.tokenizer(
            str(self.texts[idx]),
            max_length=self.max_len,
            padding="max_length",
            truncation=True,
            return_tensors="pt"
        )
        return {
            "input_ids":      enc["input_ids"].squeeze(),
            "attention_mask": enc["attention_mask"].squeeze(),
            "labels":         torch.tensor(self.labels[idx], dtype=torch.long)
        }

# 모델 로드
tokenizer = AutoTokenizer.from_pretrained("snunlp/KR-FinBert-SC")
model = AutoModelForSequenceClassification.from_pretrained("snunlp/KR-FinBert-SC", num_labels=2)

# 추론 예시
def predict_sentiment(text):
    inputs = tokenizer(text, return_tensors="pt", max_length=128,
                        truncation=True, padding=True).to(device)
    with torch.no_grad():
        outputs = model(**inputs)
    probs = torch.softmax(outputs.logits, dim=-1)
    label = probs.argmax().item()
    conf  = probs.max().item()
    sentiment = "긍정" if label == 1 else "부정"
    print(f"[{sentiment}] 신뢰도: {conf:.2%}")

predict_sentiment("오늘 개봉한 영화 완전 강추합니다!")

9-3. GPU 활용 & 성능 최적화 팁

# === 현업 GPU 최적화 팁 모음 ===

# 1. Mixed Precision Training (학습 속도 2-3배 향상)
from torch.cuda.amp import GradScaler, autocast
scaler = GradScaler()

for batch in train_loader:
    optimizer.zero_grad()
    with autocast():  # FP16 연산
        outputs = model(batch)
        loss = criterion(outputs, labels)
    scaler.scale(loss).backward()
    scaler.unscale_(optimizer)
    torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
    scaler.step(optimizer)
    scaler.update()

# 2. 다중 GPU (DataParallel)
if torch.cuda.device_count() > 1:
    model = nn.DataParallel(model)

# 3. torch.compile (PyTorch 2.0+, 최대 2배 속도 향상)
model = torch.compile(model)

# 4. pin_memory + num_workers 최적화
loader = DataLoader(dataset, batch_size=256,
    num_workers=4,   # CPU 코어 수에 맞게
    pin_memory=True, # GPU 전송 속도 향상
    prefetch_factor=2
)

# 5. Gradient Accumulation (작은 GPU에서 큰 배치 효과)
accum_steps = 4  # 실효 배치 = batch_size * 4
for i, (images, labels) in enumerate(train_loader):
    loss = criterion(model(images), labels) / accum_steps
    loss.backward()
    if (i+1) % accum_steps == 0:
        optimizer.step()
        optimizer.zero_grad()

Ch 10. 현업 면접 Q&A TOP 10

딥러닝 개발자 면접에서 반드시 나오는 핵심 질문 & 완벽 답변

Q1. 기울기 소실(Vanishing Gradient) 문제와 해결책은?

Sigmoid/Tanh 사용 시 역전파에서 기울기가 0에 수렴하는 문제입니다. 해결책: ReLU 계열 활성화 함수 사용, Batch Normalization 적용, ResNet의 잔차 연결(Skip Connection), 기울기 클리핑(Gradient Clipping)

Q2. 배치 정규화(Batch Normalization)의 효과는?

Internal Covariate Shift(층간 입력 분포 변화) 문제를 해결합니다. 학습 안정화, 더 높은 학습률 사용 가능, Dropout 대체/보완, 수렴 속도 향상. 추론 시 학습 통계의 이동 평균을 사용합니다.

Q3. Dropout이 과적합을 방지하는 원리는?

학습 중 p 확률로 뉴런을 랜덤 비활성화하여 앙상블 효과를 만들어냅니다. 특정 뉴런에 의존하지 않도록 강제합니다. 추론 시에는 전체 뉴런을 사용하되 출력에 (1-p)를 곱해 기댓값을 맞춥니다.

Q4. Adam 옵티마이저의 동작 원리는?

Momentum(1차 모멘트)과 RMSProp(2차 모멘트)를 결합한 방법입니다. m_t = beta1*m_{t-1} + (1-beta1)*g, v_t = beta2*v_{t-1} + (1-beta2)*g^2. 편향 보정 후 학습률 조정: lr * m_hat / (sqrt(v_hat) + eps)

Q5. CNN에서 Pooling의 역할은? Max vs Average?

공간 크기를 줄여 파라미터 수 감소 및 위치 불변성을 만듭니다. MaxPooling은 주요 특징 유지(분류 강점), AveragePooling은 부드러운 특징 보존(Global 시 선호). 현대 아키텍처는 GAP(Global Average Pooling) 선호합니다.

Q6. Self-Attention과 RNN의 차이는?

RNN: 순차적 처리(병렬화 불가), 장거리 의존성 약함. Self-Attention: 모든 위치를 동시에 비교(O(1) 패스), 장거리 의존성 직접 포착, 병렬 처리 가능, 연산량 O(n^2)이 단점

Q7. Transfer Learning vs Fine-tuning 차이는?

Transfer Learning: 사전학습 모델의 가중치를 동결하고 새 헤드만 학습(Feature Extraction). Fine-tuning: 일부 또는 전체 레이어를 낮은 학습률로 재학습. 데이터 크기와 도메인 유사도에 따라 선택합니다.

Q8. Learning Rate 설정 방법은?

LR Finder: 작은 값에서 시작해 손실이 최소인 지점 찾기. 논문 기본값 사용 (Adam: 1e-3, BERT: 2e-5). Grid/Random Search. Warmup + Cosine Decay 스케줄러로 동적 조정. OneCycleLR이 현업에서 강력합니다.

Q9. 모델 경량화 방법들을 설명하세요.

Pruning: 중요도 낮은 가중치 제거. Quantization: FP32→INT8 변환(4x 경량화). Knowledge Distillation: 큰 모델 지식을 작은 모델에 전수. LoRA: 저랭크 분해로 파인튜닝. TFLite/ONNX 변환으로 배포 최적화

Q10. 딥러닝 모델 배포 시 고려사항은?

추론 속도 vs 정확도 트레이드오프. 모델 직렬화 (ONNX, TorchScript, TF SavedModel). 버전 관리 (MLflow, DVC). 모니터링 (데이터 드리프트, 성능 저하 감지). A/B 테스트. 롤백 전략 수립

학습 완료 체크리스트

Ch01~Ch03 기초

☐ 퍼셉트론, 활성화 함수 이해
☐ 역전파/경사하강법 직접 구현
☐ Keras Sequential/Functional API 숙달
☐ PyTorch 학습 루프 처음부터 작성

Ch04~Ch06 심화

☐ CNN으로 CIFAR-10 분류 90%+
☐ LSTM으로 시계열/텍스트 처리
☐ Self-Attention 코드 직접 구현
☐ BERT/GPT 구조 설명 가능

Ch07~Ch08 실전기술

☐ HuggingFace Trainer로 파인튜닝
☐ LoRA 적용하여 경량 파인튜닝
☐ Mixed Precision Training 적용
☐ 학습률 스케줄러 적재적소 사용

Ch09~Ch10 프로젝트

☐ MNIST 99%+ 정확도 달성
☐ 한국어 감성 분석 서비스 구축
☐ 면접 Q&A 10개 자신감 있게 답변
☐ GitHub 포트폴리오 프로젝트 업로드

Guide 05 완료!

딥러닝 완전 정복 — 신경망의 원리부터 현업 배포까지

이 가이드를 완료했다면 딥러닝 모델을 직접 설계하고 학습·배포할 수 있는 수준이 되었습니다.
다음 가이드에서는 ChatGPT API, LangChain, RAG 등 생성형 AI 개발을 배웁니다.

다음 단계

AiDevGuide0006

생성형 AI 완전 정복

추천 실습

Kaggle 대회 참가

딥러닝 경진대회