# Part 7: 딥러닝 기초와 PyTorch **⬅️ 이전 시간: [Part 6: 머신러닝 모델링과 평가](./part_6_machine_learning.md)** **➡️ 다음 시간: [Part 7.5: LangChain으로 LLM 애플리케이션 개발 맛보기](./part_7.5_llm_application_development_with_langchain.md)** --- ## 1. 학습 목표 (Learning Objectives) 이번 파트가 끝나면, 여러분은 다음을 할 수 있게 됩니다. - 딥러닝과 머신러닝의 차이점을 설명하고, PyTorch의 역할을 이해할 수 있습니다. - PyTorch `Tensor`를 다루고, `nn.Module`을 상속받아 직접 인공 신경망 모델을 설계할 수 있습니다. - 손실 함수, 옵티마이저의 역할을 이해하고, 딥러닝의 핵심인 학습 루프(Training Loop)를 구현할 수 있습니다. - 순전파(Forward Propagation)와 역전파(Backward Propagation)의 기본 개념을 설명할 수 있습니다. - `torchvision`을 사용하여 표준 데이터셋(MNIST)을 불러오고, 간단한 CNN 모델을 만들어 이미지 분류 문제를 해결할 수 있습니다. ## 2. 핵심 키워드 (Keywords) `딥러닝(Deep Learning)`, `PyTorch`, `텐서(Tensor)`, `인공 신경망(ANN)`, `nn.Module`, `활성화 함수(Activation Function)`, `손실 함수(Loss Function)`, `옵티마이저(Optimizer)`, `순전파(Forward Propagation)`, `역전파(Backward Propagation)`, `에포크(Epoch)`, `CNN(Convolutional Neural Network)` ## 3. 도입: 모델을 직접 조립하는 즐거움, PyTorch (Introduction) 드디어 딥러닝의 세계에 입문합니다. 지난 시간까지 우리는 Scikit-learn이라는 강력한 '완성품'을 사용했다면, 이제부터는 **PyTorch**라는 '조립 키트'를 사용하여 우리만의 모델을 직접 만들어 볼 것입니다. > **💡 비유: PyTorch는 '레고 블록', Scikit-learn은 '완성된 장난감'** > - **Scikit-learn**: '트럭', '비행기'처럼 이미 완성된 모델을 가져와 사용하는 **'완성품 장난감'**과 같습니다. 사용하기 편리하고 대부분의 정형 데이터 문제에 효과적입니다. > - **PyTorch**: 다양한 모양의 **'레고 블록'**을 제공하여, 사용자가 원하는 어떤 복잡한 구조(신경망)라도 직접 조립할 수 있게 해줍니다. 이미지, 텍스트 등 비정형 데이터를 다루는 딥러닝에 필수적이며, 연구와 산업 현장에서 가장 널리 쓰이는 도구 중 하나입니다. > [!TIP] > 본 파트의 모든 예제 코드는 `source_code/part_7_deep_learning.py` 파일에서 직접 실행하고 수정해볼 수 있습니다. --- ## 4. PyTorch 딥러닝의 기본 구성 요소 > **🎯 1-2일차 목표:** PyTorch의 텐서를 이해하고, 인공 신경망의 기본 구조를 코드로 구현합니다. ### 4-1. 딥러닝의 데이터, 텐서(Tensor) - **텐서**: PyTorch에서 데이터를 다루는 기본 단위입니다. NumPy의 `ndarray`와 매우 유사하지만, 두 가지 결정적인 차이가 있습니다. 1. **GPU 가속**: `.to('cuda')` 코드 한 줄로 GPU를 사용한 초고속 연산이 가능합니다. 2. **자동 미분(Autograd)**: 딥러닝 학습의 핵심인 '역전파'를 위해 자동으로 미분값을 계산해줍니다. ### 4-2. 인공 신경망(ANN) 모델 만들기 PyTorch에서는 `torch.nn.Module`을 상속받아 우리만의 신경망 클래스(레고 조립 설명서)를 정의합니다. - **`__init__(self)`**: 모델에 필요한 레고 블록들(레이어)을 정의하는 곳. - **`forward(self, x)`**: 레고 블록들을 어떤 순서로 조립할지(데이터 흐름) 정의하는 곳. ```python import torch import torch.nn as nn # 모델 정의 (레고 조립 설명서) class SimpleNet(nn.Module): # 1. 필요한 레고 블록 선언 def __init__(self, input_size, hidden_size, num_classes): super(SimpleNet, self).__init__() self.fc1 = nn.Linear(input_size, hidden_size) # 완전 연결 계층 self.relu = nn.ReLU() # 활성화 함수 self.fc2 = nn.Linear(hidden_size, num_classes) # 2. 레고 블록 조립 순서 정의 def forward(self, x): # 순전파: 데이터가 입력층부터 출력층까지 나아가는 과정 out = self.fc1(x) out = self.relu(out) # 💡 비선형성을 추가하여 모델의 표현력을 높임 out = self.fc2(out) return out model = SimpleNet(input_size=784, hidden_size=500, num_classes=10) # MNIST 예시 print(model) ``` > ### 심화: 활성화 함수, 왜 필요한가? (Deep Dive: Activation Functions) > > **Q: 왜 굳이 `ReLU` 같은 활성화 함수를 중간에 삽입해야 할까요? 선형 계층(`nn.Linear`)만 여러 개 쌓으면 더 간단하지 않을까요?** > > **A: 모델에 '비선형성(Non-linearity)'을 부여하기 위해서입니다.** 만약 활성화 함수 없이 선형 계층만 계속 쌓는다면, 이는 결국 하나의 거대한 선형 계층을 쌓은 것과 수학적으로 동일합니다. > > - **선형 변환의 한계**: 선형 변환은 데이터를 늘리거나, 줄이거나, 회전시키는 것은 가능하지만, 구불구불한 복잡한 형태의 데이터를 구분하는 경계선을 만들 수는 없습니다. > - **비선형성의 마법**: 활성화 함수는 이러한 선형 변환 결과에 '비틀기'나 '접기'와 같은 비선형적인 변환을 가합니다. 이 과정을 여러 층 반복함으로써, 신경망은 아무리 복잡한 데이터 분포라도 학습하고 구분해낼 수 있는 유연하고 강력한 표현력(Expressiveness)을 갖추게 됩니다. > > **주요 활성화 함수 비교** > > | 활성화 함수 | 수식 / 특징 | 장점 | 단점 | 주요 사용처 | > |---|---|---|---|---| > | **Sigmoid** | `1 / (1 + e^-x)`
출력을 (0, 1) 사이로 압축 | 출력을 확률처럼 해석 가능 | **Vanishing Gradient** 문제 발생
출력의 중심이 0이 아님 | 이진 분류 문제의 출력층 | > | **Tanh** | `(e^x - e^-x) / (e^x + e^-x)`
출력을 (-1, 1) 사이로 압축 | 출력의 중심이 0 (Sigmoid 단점 보완) | **Vanishing Gradient** 문제 여전히 존재 | RNN의 은닉층에서 종종 사용 | > | **ReLU** | `max(0, x)`
입력이 0보다 작으면 0, 크면 그대로 출력 | 계산이 매우 빠름
Vanishing Gradient 문제 해결 | **Dying ReLU** 문제 (입력이 음수면 뉴런이 죽음) | 대부분의 딥러닝 모델의 은닉층 (가장 대중적) | > | **Leaky ReLU** | `max(α*x, x)` (α는 작은 수, 예: 0.01)
ReLU의 변형 | Dying ReLU 문제 해결 | α 값을 직접 설정해야 함 | ReLU 대신 사용해볼 수 있는 대안 | >| **Softmax** | `e^zi / Σ(e^zj)`
다중 클래스 분류의 출력을 확률 분포로 변환 | 각 클래스에 대한 확률을 직관적으로 보여줌 | - | 다중 클래스 분류 문제의 출력층 | > > > --- > > ## 5. 딥러닝 모델 학습 과정 > > > **🎯 3-4일차 목표:** 손실 함수, 옵티마이저의 개념을 이해하고 전체 학습 루프를 구현합니다. > > ### 5-1. 모델의 예측을 어떻게 평가할까? (손실 함수) > > 훌륭한 모델을 만들기 위해서는, 현재 모델이 얼마나 '잘하고' 또는 '못하고' 있는지를 측정할 객관적인 '평가 지표'가 필요합니다. 이 역할을 하는 것이 바로 **손실 함수(Loss Function)** 입니다. > > - **손실 함수(Loss Function)**: 모델의 예측값(`prediction`)과 실제 정답(`target`) 사이의 차이, 즉 **'오차(Error)'를 계산하는 함수**입니다. 딥러닝 학습의 목표는 이 손실 함수가 계산한 값을 **최소화(Minimize)**하는 것입니다. > - **옵티마이저(Optimizer)**: 손실 함수가 계산한 오차를 기반으로, 모델의 파라미터(가중치)를 어느 방향으로 얼마나 업데이트할지 결정하는 **'조율사'**. (예: `torch.optim.Adam`) > > ### 심화: 대표적인 손실 함수 (MSE vs Cross-Entropy) > > 손실 함수는 해결하려는 문제의 종류에 따라 적절한 것을 선택해야 합니다. > > #### 1. 평균 제곱 오차 (Mean Squared Error, MSE) > - **개념**: 예측값과 실제 정답 사이의 '거리'를 기반으로 오차를 측정합니다. 각 데이터 포인트의 오차를 제곱하고, 이를 모두 더한 후 평균을 냅니다. > - **언제 사용하나?**: **회귀(Regression)** 문제. (예: 집값 예측, 주가 예측 등 연속적인 값을 예측할 때) > - **PyTorch**: `nn.MSELoss()` > - **예시**: > - 정답: `25.0` / 예측: `24.5` -> 오차: `(25.0 - 24.5)^2 = 0.25` > - 정답: `25.0` / 예측: `20.0` -> 오차: `(25.0 - 20.0)^2 = 25.0` (오차가 클수록 손실이 기하급수적으로 커짐) > > #### 2. 교차 엔트로피 오차 (Cross-Entropy Loss) > - **개념**: 모델이 예측한 '확률 분포'와 실제 정답의 '확률 분포'가 얼마나 다른지를 측정합니다. 모델이 정답 클래스를 얼마나 높은 확률로 예측했는지에 따라 손실이 결정됩니다. > - **언제 사용하나?**: **분류(Classification)** 문제. (예: 이미지 분류, 스팸 메일 분류 등 카테고리를 예측할 때) > - **PyTorch**: `nn.CrossEntropyLoss()` > - `nn.CrossEntropyLoss`는 내부적으로 `Softmax` 함수를 적용한 후 `NLLLoss`를 계산하므로, 모델의 마지막 레이어에 별도의 `Softmax`를 추가할 필요가 없습니다. > - **예시 (3개 클래스 분류)**: > - 정답: `[1, 0, 0]` (첫 번째 클래스가 정답) > - 예측 A (잘 맞춤): `[0.9, 0.05, 0.05]` -> 낮은 손실(Loss) > - 예측 B (못 맞춤): `[0.1, 0.8, 0.1]` -> 높은 손실(Loss) > > ### 5-2. 핵심 프로세스: 학습 루프(Training Loop) 딥러닝 모델 학습은 '에포크(Epoch)'라는 단위로 반복됩니다. 1 에포크는 전체 훈련 데이터를 한 번 모두 사용하는 것을 의미하며, 학습은 다음 4단계로 이루어집니다. 1. **`model(inputs)`**: 모델에 입력을 넣어 예측값(`outputs`)을 계산합니다 (**순전파**). 2. **`loss = criterion(outputs, labels)`**: 예측값과 실제 정답을 비교하여 오차(`loss`)를 계산합니다. 3. **`loss.backward()`**: 계산된 오차를 기반으로, 각 파라미터가 오차에 얼마나 기여했는지 미분값을 계산합니다 (**역전파**). 4. **`optimizer.step()`**: 계산된 미분값을 바탕으로 모델의 파라미터를 업데이트하여 오차를 줄이는 방향으로 나아갑니다. > **💡 순전파와 역전파** > - **순전파 (Forward)**: 내가 만든 레시피로 요리를 해서(모델 예측) 손님에게 내놓는 과정. > - **역전파 (Backward)**: 손님의 피드백("너무 짜요!")을 듣고, 소금을 얼마나 많이 넣었는지, 간장을 얼마나 넣었는지 원인을 거슬러 올라가 분석하는 과정. `optimizer.step()`은 이 분석을 바탕으로 다음 요리에서는 소금을 덜 넣는 행동입니다. --- ## 6. 직접 해보기 (Hands-on Lab): MNIST 손글씨 분류 > **🎯 5일차 목표:** PyTorch를 사용하여 딥러닝의 "Hello, World!"인 MNIST 손글씨 분류기를 직접 만듭니다. ### 문제: `torchvision`을 사용하여 MNIST 데이터셋을 불러오고, 간단한 CNN(합성곱 신경망) 모델을 구축하여 손글씨 숫자를 분류하는 전체 코드를 완성하세요. ```python import torch import torch.nn as nn import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader # 1. 데이터셋 및 로더 준비 transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]) train_dataset = torchvision.datasets.MNIST(root='./data', train=True, transform=transform, download=True) test_dataset = torchvision.datasets.MNIST(root='./data', train=False, transform=transform) train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False) # 2. CNN 모델 정의 class ConvNet(nn.Module): def __init__(self): super(ConvNet, self).__init__() self.layer1 = nn.Sequential( nn.Conv2d(1, 32, kernel_size=5, stride=1, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2)) self.layer2 = nn.Sequential( nn.Conv2d(32, 64, kernel_size=5, stride=1, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2)) self.fc = nn.Linear(7*7*64, 10) def forward(self, x): out = self.layer1(x) out = self.layer2(out) out = out.reshape(out.size(0), -1) # Flatten out = self.fc(out) return out # 3. 모델, 손실함수, 옵티마이저 설정 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = ConvNet().to(device) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 4. 학습 루프 구현 # for epoch ... # for i, (images, labels) in enumerate(train_loader): # - images, labels를 device로 이동 # - 순전파, 손실 계산, 역전파, 파라미터 업데이트 코드 작성 # 5. 평가 루프 구현 # with torch.no_grad(): # - test_loader에서 데이터를 가져와 예측 # - 전체 테스트 정확도 계산 및 출력 코드 작성 ``` --- ## 7. 되짚어보기 (Summary) 이번 주 우리는 PyTorch라는 '레고 블록'으로 딥러닝 모델을 직접 조립하는 방법을 배웠습니다. - **PyTorch의 구성요소**: GPU 연산이 가능한 `Tensor`와, `nn.Module`을 상속받아 만드는 우리만의 모델 구조를 이해했습니다. - **딥러닝 학습 과정**: '오차 측정기'인 **손실 함수**와 '조율사'인 **옵티마이저**를 사용하여, **순전파 → 손실 계산 → 역전파 → 파라미터 업데이트**로 이어지는 학습 루프의 원리를 파악했습니다. - **실전 경험**: MNIST 손글씨 분류 실습을 통해 이미지 데이터를 다루는 CNN 모델을 직접 구현하고 학습시키는 전 과정을 체험했습니다. ## 8. 더 깊이 알아보기 (Further Reading) - [PyTorch 공식 60분 튜토리얼](https://tutorials.pytorch.kr/beginner/deep_learning_60min_blitz.html): PyTorch의 핵심 기능을 빠르게 훑어볼 수 있는 최고의 가이드 - [cs231n: Convolutional Neural Networks for Visual Recognition](https://cs231n.github.io/): 스탠포드 대학의 전설적인 딥러닝 강의. CNN의 원리를 깊이 있게 이해하고 싶다면 필독. - [A Comprehensive Introduction to Torch.nn for Deep Learning](https://www.assemblyai.com/blog/a-comprehensive-introduction-to-torch-nn-for-deep-learning/): `torch.nn` 모듈에 대한 상세한 설명 --- **➡️ 다음 시간: [Part 7.5: LangChain으로 LLM 애플리케이션 개발 맛보기](./part_7.5_llm_application_development_with_langchain.md)**