Guide Keras에서 PyTorch로 전환하기

케라스 사용자를 위한 파이토치 완벽 비교 가이드 - 핵심 개념부터 실전 코드까지

🎯 1. 철학의 차이: 고수준 vs 저수준

Keras와 PyTorch는 근본적으로 다른 설계 철학을 가지고 있습니다. 이 차이를 이해하는 것이 전환의 첫 걸음입니다.

🔍 핵심 철학 비교

특징 Keras PyTorch
추상화 수준 매우 높음 (High-level API) 중간~낮음 (Mid-to-low level API)
설계 목표 빠른 프로토타이핑, 사용 편의성 유연성, 연구 친화성, 세밀한 제어
코드 스타일 선언적 (Declarative) 명령형 (Imperative)
디버깅 제한적 (그래프 내부 접근 어려움) 쉬움 (Python 디버거 직접 사용)
동적 그래프 제한적 지원 기본 지원 (Define-by-Run)
학습 곡선 매우 완만함 중간 (Python과 NumPy 지식 필요)
커뮤니티 초보자, 엔지니어 중심 연구자, 논문 구현 중심
💡 선택 가이드:
Keras를 계속 써야 할 때: 빠른 MVP 개발, 표준적인 아키텍처 사용, 프로덕션 배포가 주 목적
PyTorch로 전환해야 할 때: 커스텀 레이어/손실함수 필요, 최신 논문 구현, 연구 프로젝트, 세밀한 제어 필요

🏗️ 2. 모델 구축 방식 비교

가장 큰 차이점 중 하나는 모델을 정의하는 방법입니다. Keras의 Sequential/Functional API와 PyTorch의 클래스 기반 접근법을 비교해봅시다.

2.1 간단한 MLP (Multi-Layer Perceptron)

🔴 Keras (Sequential API)
from tensorflow import keras
from tensorflow.keras import layers

# 모델 정의
model = keras.Sequential([
    layers.Dense(128, activation='relu', 
                 input_shape=(784,)),
    layers.Dropout(0.2),
    layers.Dense(64, activation='relu'),
    layers.Dropout(0.2),
    layers.Dense(10, activation='softmax')
])

# 컴파일
model.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

# 학습
model.fit(x_train, y_train, 
          epochs=10, 
          batch_size=32,
          validation_split=0.2)
🟠 PyTorch
import torch
import torch.nn as nn
import torch.optim as optim

# 모델 정의
class MLP(nn.Module):
    def __init__(self):
        super(MLP, self).__init__()
        self.fc1 = nn.Linear(784, 128)
        self.dropout1 = nn.Dropout(0.2)
        self.fc2 = nn.Linear(128, 64)
        self.dropout2 = nn.Dropout(0.2)
        self.fc3 = nn.Linear(64, 10)
    
    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.dropout1(x)
        x = torch.relu(self.fc2(x))
        x = self.dropout2(x)
        x = self.fc3(x)
        return x

model = MLP()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())

# 학습 루프는 직접 작성 (다음 섹션 참조)
⚠️ 핵심 차이점:
1. 모델 정의: Keras는 레이어를 리스트로 쌓지만, PyTorch는 클래스를 정의
2. Forward Pass: PyTorch는 forward() 메서드에서 데이터 흐름을 명시적으로 정의
3. 컴파일: Keras는 compile()로 한 번에 설정, PyTorch는 개별 객체 생성
4. 학습: Keras는 fit() 한 줄, PyTorch는 학습 루프를 직접 작성해야 함

2.2 복잡한 모델 (Functional API vs nn.Module)

🔴 Keras (Functional API)
from tensorflow.keras import Input, Model
from tensorflow.keras.layers import (
    Dense, Concatenate
)

# 다중 입력
input1 = Input(shape=(10,))
input2 = Input(shape=(20,))

# 첫 번째 브랜치
x1 = Dense(64, activation='relu')(input1)
x1 = Dense(32, activation='relu')(x1)

# 두 번째 브랜치
x2 = Dense(64, activation='relu')(input2)
x2 = Dense(32, activation='relu')(x2)

# 병합
merged = Concatenate()([x1, x2])
output = Dense(1, activation='sigmoid')(merged)

# 모델 생성
model = Model(
    inputs=[input1, input2], 
    outputs=output
)

model.compile(optimizer='adam', 
              loss='binary_crossentropy')
🟠 PyTorch
import torch.nn as nn

class MultiInputModel(nn.Module):
    def __init__(self):
        super().__init__()
        # 첫 번째 브랜치
        self.branch1 = nn.Sequential(
            nn.Linear(10, 64),
            nn.ReLU(),
            nn.Linear(64, 32),
            nn.ReLU()
        )
        
        # 두 번째 브랜치
        self.branch2 = nn.Sequential(
            nn.Linear(20, 64),
            nn.ReLU(),
            nn.Linear(64, 32),
            nn.ReLU()
        )
        
        # 출력 레이어
        self.output = nn.Linear(64, 1)
    
    def forward(self, input1, input2):
        x1 = self.branch1(input1)
        x2 = self.branch2(input2)
        merged = torch.cat([x1, x2], dim=1)
        return torch.sigmoid(self.output(merged))

model = MultiInputModel()
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters())

🔄 3. 학습 루프: fit() vs 직접 구현

Keras의 가장 큰 장점은 model.fit() 한 줄로 학습이 끝난다는 것입니다. PyTorch는 학습 루프를 직접 작성해야 하지만, 이것이 오히려 세밀한 제어를 가능하게 합니다.

🔴 Keras 학습
# 한 줄로 끝!
history = model.fit(
    x_train, y_train,
    batch_size=32,
    epochs=10,
    validation_data=(x_val, y_val),
    callbacks=[
        keras.callbacks.EarlyStopping(
            patience=3
        ),
        keras.callbacks.ModelCheckpoint(
            'best_model.h5'
        )
    ]
)

# 평가
test_loss, test_acc = model.evaluate(
    x_test, y_test
)

# 예측
predictions = model.predict(x_test)
🟠 PyTorch 학습 루프
from torch.utils.data import DataLoader, TensorDataset

# 데이터로더 준비
train_dataset = TensorDataset(x_train, y_train)
train_loader = DataLoader(
    train_dataset, 
    batch_size=32, 
    shuffle=True
)

# 학습 루프
model.train()
for epoch in range(10):
    running_loss = 0.0
    for inputs, labels in train_loader:
        # 그래디언트 초기화
        optimizer.zero_grad()
        
        # Forward pass
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        
        # Backward pass
        loss.backward()
        
        # 파라미터 업데이트
        optimizer.step()
        
        running_loss += loss.item()
    
    print(f'Epoch {epoch+1}, Loss: {running_loss/len(train_loader):.4f}')

# 평가
model.eval()
with torch.no_grad():
    test_outputs = model(x_test)
    test_loss = criterion(test_outputs, y_test)

# 예측
predictions = model(x_test)

🔄 학습 루프 상세 비교

단계 Keras PyTorch
배치 처리 자동 (fit 내부에서) DataLoader로 명시적 구현
Forward Pass 내부 처리 outputs = model(inputs)
손실 계산 컴파일 시 지정한 함수 사용 loss = criterion(outputs, labels)
Backward Pass 자동 loss.backward() 명시적 호출
그래디언트 초기화 자동 optimizer.zero_grad() 필수!
파라미터 업데이트 자동 optimizer.step()
평가 모드 자동 전환 model.eval() / model.train()
🟠 PyTorch 학습 루프의 장점:
1. 완전한 제어: 각 단계에서 원하는 작업 수행 가능 (그래디언트 클리핑, 커스텀 로깅 등)
2. 디버깅 용이: 각 단계에 breakpoint 설정 가능
3. 유연성: 특수한 학습 방식(GAN, RL 등) 쉽게 구현
4. 명확성: 내부에서 무슨 일이 일어나는지 정확히 알 수 있음

📊 4. 데이터 처리 비교

데이터를 불러오고 전처리하는 방식도 크게 다릅니다.

🔴 Keras 데이터 처리
from tensorflow.keras.preprocessing.image import (
    ImageDataGenerator
)

# 데이터 증강
datagen = ImageDataGenerator(
    rotation_range=20,
    width_shift_range=0.2,
    height_shift_range=0.2,
    horizontal_flip=True,
    rescale=1./255
)

# 디렉토리에서 데이터 로드
train_generator = datagen.flow_from_directory(
    'data/train',
    target_size=(224, 224),
    batch_size=32,
    class_mode='categorical'
)

# 학습
model.fit(
    train_generator,
    epochs=10,
    steps_per_epoch=100
)
🟠 PyTorch 데이터 처리
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import os

# 커스텀 데이터셋
class CustomDataset(Dataset):
    def __init__(self, root_dir, transform=None):
        self.root_dir = root_dir
        self.transform = transform
        self.images = os.listdir(root_dir)
    
    def __len__(self):
        return len(self.images)
    
    def __getitem__(self, idx):
        img_path = os.path.join(
            self.root_dir, 
            self.images[idx]
        )
        image = Image.open(img_path)
        label = ...  # 레이블 로직
        
        if self.transform:
            image = self.transform(image)
        
        return image, label

# 데이터 증강
transform = transforms.Compose([
    transforms.RandomRotation(20),
    transforms.RandomHorizontalFlip(),
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225]
    )
])

dataset = CustomDataset(
    'data/train', 
    transform=transform
)
train_loader = DataLoader(
    dataset, 
    batch_size=32, 
    shuffle=True,
    num_workers=4
)

4.1 내장 데이터셋 사용

🔴 Keras
from tensorflow.keras.datasets import mnist

# 데이터 로드
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# 전처리
x_train = x_train.reshape(-1, 784) / 255.0
x_test = x_test.reshape(-1, 784) / 255.0

# 바로 학습
model.fit(x_train, y_train, epochs=5)
🟠 PyTorch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# 데이터 변환 정의
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])

# 데이터셋 로드
train_dataset = datasets.MNIST(
    'data', 
    train=True, 
    download=True,
    transform=transform
)

# 데이터로더 생성
train_loader = DataLoader(
    train_dataset, 
    batch_size=64, 
    shuffle=True
)

# 학습 루프에서 사용
for images, labels in train_loader:
    # 학습 코드...

🎨 5. CNN 아키텍처 비교

합성곱 신경망을 구축하는 방법을 비교해봅시다.

🔴 Keras CNN
from tensorflow.keras import layers, models

model = models.Sequential([
    layers.Conv2D(32, (3, 3), activation='relu', 
                  input_shape=(28, 28, 1)),
    layers.MaxPooling2D((2, 2)),
    
    layers.Conv2D(64, (3, 3), activation='relu'),
    layers.MaxPooling2D((2, 2)),
    
    layers.Conv2D(64, (3, 3), activation='relu'),
    
    layers.Flatten(),
    layers.Dense(64, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')
])

model.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

model.summary()
🟠 PyTorch CNN
import torch.nn as nn
import torch.nn.functional as F

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, 3)
        self.pool = nn.MaxPool2d(2, 2)
        
        self.conv2 = nn.Conv2d(32, 64, 3)
        self.conv3 = nn.Conv2d(64, 64, 3)
        
        self.fc1 = nn.Linear(64 * 1 * 1, 64)
        self.dropout = nn.Dropout(0.5)
        self.fc2 = nn.Linear(64, 10)
    
    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = F.relu(self.conv3(x))
        
        x = x.view(-1, 64 * 1 * 1)  # Flatten
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)
        return x

model = CNN()

# 모델 요약 (torchsummary 사용)
from torchsummary import summary
summary(model, (1, 28, 28))

💾 6. 모델 저장 및 불러오기

🔴 Keras
# 전체 모델 저장 (아키텍처 + 가중치)
model.save('my_model.h5')
model.save('my_model.keras')  # 새로운 형식

# 가중치만 저장
model.save_weights('weights.h5')

# 모델 불러오기
from tensorflow.keras.models import load_model
loaded_model = load_model('my_model.h5')

# 가중치만 불러오기
model.load_weights('weights.h5')

# TensorFlow SavedModel 형식
model.save('saved_model/')
loaded = tf.keras.models.load_model('saved_model/')
🟠 PyTorch
# 가중치만 저장 (일반적인 방법)
torch.save(model.state_dict(), 'model_weights.pth')

# 전체 모델 저장 (비추천)
torch.save(model, 'entire_model.pth')

# 체크포인트 저장 (최선의 방법)
torch.save({
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'loss': loss,
}, 'checkpoint.pth')

# 가중치 불러오기
model = CNN()  # 모델 클래스 정의 필요!
model.load_state_dict(
    torch.load('model_weights.pth')
)
model.eval()

# 체크포인트 불러오기
checkpoint = torch.load('checkpoint.pth')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']
⚠️ PyTorch 저장/불러오기 주의사항:
1. PyTorch는 모델 아키텍처를 저장하지 않습니다. 불러올 때 모델 클래스 정의가 필요합니다.
2. state_dict()를 사용하는 것이 권장되며, 전체 모델 저장은 비추천됩니다.
3. GPU에서 학습한 모델을 CPU에서 불러올 때는 map_location='cpu' 파라미터가 필요합니다.

🔧 7. 주요 레이어 및 함수 대응표

📋 레이어 대응표

기능 Keras PyTorch
완전연결층 Dense(units) nn.Linear(in_features, out_features)
2D 합성곱 Conv2D(filters, kernel_size) nn.Conv2d(in_channels, out_channels, kernel_size)
맥스 풀링 MaxPooling2D(pool_size) nn.MaxPool2d(kernel_size)
배치 정규화 BatchNormalization() nn.BatchNorm2d(num_features)
드롭아웃 Dropout(rate) nn.Dropout(p)
평탄화 Flatten() view() 또는 flatten()
LSTM LSTM(units) nn.LSTM(input_size, hidden_size)
임베딩 Embedding(input_dim, output_dim) nn.Embedding(num_embeddings, embedding_dim)

🎯 활성화 함수 대응표

활성화 함수 Keras PyTorch
ReLU activation='relu' F.relu() 또는 nn.ReLU()
Sigmoid activation='sigmoid' torch.sigmoid() 또는 nn.Sigmoid()
Tanh activation='tanh' torch.tanh() 또는 nn.Tanh()
Softmax activation='softmax' F.softmax(dim=1) 또는 nn.Softmax(dim=1)
LeakyReLU LeakyReLU(alpha) F.leaky_relu() 또는 nn.LeakyReLU()

📉 손실 함수 대응표

손실 함수 Keras PyTorch
이진 교차 엔트로피 binary_crossentropy nn.BCELoss() 또는 nn.BCEWithLogitsLoss()
범주형 교차 엔트로피 categorical_crossentropy nn.CrossEntropyLoss() (소프트맥스 포함)
희소 범주형 교차 엔트로피 sparse_categorical_crossentropy nn.CrossEntropyLoss()
평균 제곱 오차 mse nn.MSELoss()
평균 절대 오차 mae nn.L1Loss()

⚙️ 옵티마이저 대응표

옵티마이저 Keras PyTorch
Adam optimizer='adam' optim.Adam(params, lr=0.001)
SGD optimizer='sgd' optim.SGD(params, lr=0.01)
RMSprop optimizer='rmsprop' optim.RMSprop(params)
AdaGrad optimizer='adagrad' optim.Adagrad(params)

🎮 8. GPU 사용

🔴 Keras (자동 처리)
import tensorflow as tf

# GPU 확인
print("GPU 사용 가능:", tf.config.list_physical_devices('GPU'))

# 자동으로 GPU 사용
# 별도 코드 불필요!

# 특정 GPU 선택
gpus = tf.config.list_physical_devices('GPU')
if gpus:
    tf.config.set_visible_devices(gpus[0], 'GPU')

# 메모리 증가 허용
for gpu in gpus:
    tf.config.experimental.set_memory_growth(gpu, True)
🟠 PyTorch (명시적 전송)
import torch

# GPU 확인
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"사용 장치: {device}")

# 모델을 GPU로 이동
model = model.to(device)

# 학습 루프에서 데이터도 GPU로 이동
for inputs, labels in train_loader:
    inputs = inputs.to(device)
    labels = labels.to(device)
    
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    # ...

# 특정 GPU 선택
device = torch.device('cuda:0')  # 첫 번째 GPU
model.to(device)

# 다중 GPU
if torch.cuda.device_count() > 1:
    model = nn.DataParallel(model)
model.to(device)
⚠️ PyTorch GPU 사용 시 주의사항:
1. 모델, 데이터 모두 .to(device)로 명시적으로 GPU로 이동해야 합니다.
2. GPU와 CPU 간 텐서를 섞어 사용하면 에러가 발생합니다.
3. 예측 결과를 NumPy로 변환할 때: output.cpu().numpy() 필요

🔍 9. 디버깅 및 검증

🔴 Keras
# 모델 요약
model.summary()

# 레이어별 출력 확인
from tensorflow.keras import Model

layer_outputs = [layer.output for layer in model.layers]
activation_model = Model(
    inputs=model.input, 
    outputs=layer_outputs
)
activations = activation_model.predict(x_test[0:1])

# 가중치 확인
for layer in model.layers:
    weights = layer.get_weights()
    print(f"{layer.name}: {len(weights)} 가중치")

# 그래디언트 확인 (복잡함)
import tensorflow as tf
with tf.GradientTape() as tape:
    predictions = model(x_train[:1])
    loss = loss_fn(y_train[:1], predictions)
grads = tape.gradient(loss, model.trainable_weights)
🟠 PyTorch
# 모델 요약 (torchsummary 사용)
from torchsummary import summary
summary(model, input_size=(1, 28, 28))

# 중간 레이어 출력 확인 (Hook 사용)
activation = {}
def get_activation(name):
    def hook(model, input, output):
        activation[name] = output.detach()
    return hook

model.fc1.register_forward_hook(get_activation('fc1'))
output = model(x)
print(activation['fc1'])

# 가중치 확인
for name, param in model.named_parameters():
    print(f"{name}: {param.shape}")

# 그래디언트 확인 (매우 쉬움!)
output = model(x)
loss = criterion(output, y)
loss.backward()

for name, param in model.named_parameters():
    if param.grad is not None:
        print(f"{name} gradient: {param.grad.norm()}")
🟠 PyTorch 디버깅의 강력함:
1. Python 디버거 직접 사용: pdb, VSCode 브레이크포인트 등
2. 그래디언트 접근 용이: 각 파라미터의 .grad로 즉시 확인
3. 동적 그래프: forward pass 중 print문으로 디버깅 가능
4. 명시적 제어: 각 단계를 직접 볼 수 있어 문제 파악이 쉬움

📝 10. 실전 예제: 전체 프로젝트 비교

간단한 이미지 분류 프로젝트를 두 프레임워크로 구현해봅시다.

시나리오: CIFAR-10 이미지 분류

🔴 Keras 전체 코드
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

# 1. 데이터 로드
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

# 2. 모델 정의
model = keras.Sequential([
    layers.Conv2D(32, 3, activation='relu', input_shape=(32, 32, 3)),
    layers.MaxPooling2D(),
    layers.Conv2D(64, 3, activation='relu'),
    layers.MaxPooling2D(),
    layers.Conv2D(64, 3, activation='relu'),
    layers.Flatten(),
    layers.Dense(64, activation='relu'),
    layers.Dense(10)
])

# 3. 컴파일
model.compile(
    optimizer='adam',
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy']
)

# 4. 학습
history = model.fit(
    x_train, y_train,
    epochs=10,
    validation_data=(x_test, y_test)
)

# 5. 평가
test_loss, test_acc = model.evaluate(x_test, y_test)
print(f'Test accuracy: {test_acc}')

# 6. 예측
predictions = model.predict(x_test[:5])

# 총 코드 줄 수: ~30줄
🟠 PyTorch 전체 코드
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

# 1. 데이터 로드
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

trainset = datasets.CIFAR10('data', train=True, download=True, transform=transform)
testset = datasets.CIFAR10('data', train=False, download=True, transform=transform)

trainloader = DataLoader(trainset, batch_size=64, shuffle=True)
testloader = DataLoader(testset, batch_size=64, shuffle=False)

# 2. 모델 정의
class CNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 32, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(32, 64, 3)
        self.conv3 = nn.Conv2d(64, 64, 3)
        self.fc1 = nn.Linear(64 * 4 * 4, 64)
        self.fc2 = nn.Linear(64, 10)
    
    def forward(self, x):
        x = self.pool(torch.relu(self.conv1(x)))
        x = self.pool(torch.relu(self.conv2(x)))
        x = torch.relu(self.conv3(x))
        x = x.view(-1, 64 * 4 * 4)
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        return x

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = CNN().to(device)

# 3. 손실함수 & 옵티마이저
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())

# 4. 학습
for epoch in range(10):
    model.train()
    running_loss = 0.0
    for inputs, labels in trainloader:
        inputs, labels = inputs.to(device), labels.to(device)
        
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
    
    print(f'Epoch {epoch+1}, Loss: {running_loss/len(trainloader):.3f}')

# 5. 평가
model.eval()
correct = 0
total = 0
with torch.no_grad():
    for inputs, labels in testloader:
        inputs, labels = inputs.to(device), labels.to(device)
        outputs = model(inputs)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f'Test accuracy: {100 * correct / total}%')

# 총 코드 줄 수: ~70줄

🚀 11. 전환 전략 및 팁

🎯 단계별 전환 가이드

  1. 1단계: PyTorch 기초 익히기
    • 텐서 연산 (torch.Tensor와 NumPy 비교)
    • nn.Module 클래스 구조 이해
    • forward() 메서드 작성법

  2. 2단계: 간단한 모델 재구현
    • Keras로 작성한 기존 프로젝트를 PyTorch로 변환
    • Linear regression, Logistic regression부터 시작
    • 결과가 동일한지 검증

  3. 3단계: 학습 루프 마스터
    • 수동 학습 루프 작성 연습
    • 검증 루프, 조기 종료 구현
    • 학습률 스케줄러 사용법 익히기

  4. 4단계: 고급 기능 습득
    • 커스텀 레이어, 손실함수 작성
    • 데이터 증강 및 커스텀 Dataset
    • 모델 체크포인팅, 로깅

  5. 5단계: 실전 프로젝트
    • 전이학습, Fine-tuning
    • 최신 논문 구현
    • PyTorch Lightning 등 고수준 라이브러리 활용
💡 유용한 PyTorch 생태계 도구:
1. PyTorch Lightning: Keras처럼 사용할 수 있는 고수준 래퍼
2. torchvision: 이미지 관련 유틸리티, 사전학습 모델
3. torchtext: NLP 데이터 처리
4. torchsummary: Keras의 model.summary()와 동일한 기능
5. tensorboard: PyTorch도 TensorBoard 사용 가능
6. ONNX: PyTorch 모델을 다른 프레임워크로 변환

⚡ 12. 흔한 실수 및 해결법

❌ 자주 하는 실수들:

1. optimizer.zero_grad() 잊어먹기
❌ 잘못된 코드:
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()  # 그래디언트가 누적됨!
✅ 올바른 코드:
optimizer.zero_grad()  # 필수!
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()

2. 평가 시 model.eval() 안 쓰기
❌ 문제: Dropout과 BatchNorm이 학습 모드로 동작
✅ 해결:
model.eval()
with torch.no_grad():
    # 평가 코드

3. GPU-CPU 텐서 혼용
❌ 에러 발생:
model = model.to('cuda')
outputs = model(inputs)  # inputs가 CPU에 있으면 에러!
✅ 해결:
inputs = inputs.to(device)
labels = labels.to(device)
outputs = model(inputs)

4. CrossEntropyLoss에 Softmax 중복 적용
❌ 잘못: PyTorch의 nn.CrossEntropyLoss()는 내부에 Softmax 포함
✅ 해결: forward에서 Softmax 제거, logits 그대로 반환
def forward(self, x):
    x = self.fc(x)
    # return F.softmax(x)  ❌
    return x  # ✅

5. 배치 차원 무시
Keras는 자동 처리하지만 PyTorch는 명시적으로 다뤄야 함
✅ view() 사용 시: x.view(-1, ...)에서 -1은 배치 크기

🎓 13. 학습 자료 추천

📚 공식 문서 및 튜토리얼

🎥 동영상 강의

📖 책

🎯 핵심 요약

🔑 반드시 기억할 핵심 차이점

영역 Keras 방식 PyTorch 방식
모델 정의 Sequential/Functional API nn.Module 클래스 상속
학습 model.fit() 한 줄 수동 학습 루프 작성
그래디언트 자동 처리 zero_grad(), backward(), step()
GPU 사용 자동 .to(device) 명시적 호출
디버깅 제한적 Python 디버거 직접 사용 가능

✨ PyTorch를 선택해야 하는 이유

🎓 학습 로드맵

  1. PyTorch 기본 문법 (텐서, autograd) - 1주
  2. 간단한 모델 구현 (MLP, CNN) - 1주
  3. 학습 루프 패턴 습득 - 1주
  4. 고급 기능 (커스텀 레이어, Dataset) - 2주
  5. 실전 프로젝트 구현 - 지속적