
📓Google Colab에서 실습하기
이 레슨은 PyTorch/GPU가 필요합니다. 노트북을 다운로드 후 Google Colab에서 열어주세요.
학습 내용
nn.Module 기초
학습 목표
이 레슨을 완료하면:
- •nn.Module이 PyTorch에서 차지하는 역할을 이해합니다
- •커스텀 모델과 레이어를 자유롭게 설계할 수 있습니다
- •forward() 메서드의 동작 원리를 설명합니다
- •파라미터를 확인하고 관리하는 방법을 익힙니다
nn.Module이란?
비유: nn.Module은 레고의 "기본 브릭"과 같습니다. 작은 브릭(개별 레이어)을 조합해 더 큰 구조물(모델)을 만들고, 그 구조물을 또다시 더 큰 구조물의 부품으로 사용할 수 있습니다. PyTorch의 모든 신경망 구성 요소는 이 기본 브릭을 기반으로 만들어집니다.
nn.Module은 PyTorch에서 모든 신경망 레이어와 모델의 기본 클래스(부모 클래스) 입니다.
| 기능 | 설명 | 예시 |
|---|---|---|
| 파라미터 관리 | 학습 가능한 가중치 자동 추적 | model.parameters() |
| 디바이스 이동 | GPU/CPU 간 이동 | model.to("cuda") |
| 모드 전환 | 학습/평가 모드 전환 | model.train() / model.eval() |
| 저장/로드 | 모델 가중치 저장 및 복원 | torch.save(model.state_dict(), ...) |
| 서브모듈 관리 | 하위 레이어 자동 등록 | model.children() |
기본 구조: 반드시 지켜야 할 규칙
python⚠️ 로컬 실행 필요import torch import torch.nn as nn class MyModel(nn.Module): def __init__(self): # [규칙 1] 반드시 부모 클래스 초기화를 호출해야 합니다 super().__init__() # [규칙 2] __init__에서 레이어를 정의합니다 self.layer1 = nn.Linear(784, 256) self.layer2 = nn.Linear(256, 10) def forward(self, x): # [규칙 3] forward에서 데이터 흐름을 정의합니다 x = torch.relu(self.layer1(x)) x = self.layer2(x) return x
왜 super().init()이 필수인가?
super().init()을 호출하지 않으면 PyTorch가 내부적으로 사용하는 파라미터 추적 시스템이 초기화되지 않습니다. 결과적으로:
- •model.parameters()가 빈 리스트를 반환합니다
- •model.to(device)가 작동하지 않습니다
- •optimizer가 학습할 파라미터를 찾지 못합니다
이것은 PyTorch 초보자가 가장 자주 겪는 오류 중 하나입니다.
자주 사용하는 레이어 총정리
완전 연결층 (Linear)
비유: nn.Linear는 "투표 시스템"입니다. 각 입력에 가중치(투표 영향력)를 곱하고 합산하여 결과를 냅니다.
python# 784개 입력을 받아 256개 출력을 만드는 레이어 linear = nn.Linear(in_features=784, out_features=256) # 내부적으로 가중치 행렬 [256, 784]와 편향 벡터 [256]을 가짐
합성곱층 (Conv2d)
python# 3채널 입력, 64채널 출력, 3x3 필터 conv = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, padding=1)
활성화 함수
pythonrelu = nn.ReLU() # max(0, x) - 음수를 0으로 sigmoid = nn.Sigmoid() # 0~1 사이로 압축 tanh = nn.Tanh() # -1~1 사이로 압축
| 활성화 함수 | 출력 범위 | 주로 사용되는 곳 |
|---|---|---|
| ReLU | [0, +무한) | 은닉층 (가장 보편적) |
| Sigmoid | (0, 1) | 이진 분류 출력층 |
| Tanh | (-1, 1) | RNN 은닉 상태 |
| Softmax | (0, 1), 합=1 | 다중 분류 출력층 |
정규화 레이어
pythonbn = nn.BatchNorm2d(64) # 배치 정규화 (CNN용) bn1d = nn.BatchNorm1d(256) # 배치 정규화 (FC용) dropout = nn.Dropout(0.5) # 50% 뉴런을 무작위로 끔
nn.Sequential: 간단한 모델 만들기
레이어가 단순히 순서대로 연결되는 경우, nn.Sequential을 사용하면 코드가 간결해집니다.
python# 방법 1: nn.Sequential 사용 (간결) model_simple = nn.Sequential( nn.Linear(784, 256), nn.ReLU(), nn.Dropout(0.5), nn.Linear(256, 10) ) # 방법 2: nn.Module 상속 (유연) class ModelFlexible(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(784, 256) self.fc2 = nn.Linear(256, 10) self.dropout = nn.Dropout(0.5) def forward(self, x): x = torch.relu(self.fc1(x)) x = self.dropout(x) x = self.fc2(x) return x
| 비교 항목 | nn.Sequential | nn.Module 상속 |
|---|---|---|
| 코드 길이 | 짧음 | 김 |
| 유연성 | 낮음 (순차적만 가능) | 높음 (분기, 잔차 연결 등) |
| 가독성 | 단순 모델에서 좋음 | 복잡 모델에서 좋음 |
| 적합한 경우 | 레이어가 순서대로 | 복잡한 데이터 흐름 |
왜 nn.Module 상속이 더 많이 사용되는가?
실제 모델은 단순히 레이어를 쌓는 것 이상입니다. 잔차 연결(ResNet), 다중 입력/출력, 조건부 분기 등을 구현하려면 forward()를 직접 작성해야 합니다.
파라미터 확인과 관리
모델이 정확히 어떤 파라미터를 가지고 있는지 확인하는 것은 디버깅과 최적화에 매우 중요합니다.
pythonmodel = ModelFlexible() # 모든 파라미터의 이름과 크기 출력 for name, param in model.named_parameters(): print("{}: {}".format(name, param.shape)) # 출력: # fc1.weight: torch.Size([256, 784]) # fc1.bias: torch.Size([256]) # fc2.weight: torch.Size([10, 256]) # fc2.bias: torch.Size([10]) # 총 파라미터 수 계산 total_params = sum(p.numel() for p in model.parameters()) print("Total parameters: {:,}".format(total_params)) # 출력: Total parameters: 203,530 # 학습 가능한 파라미터만 세기 trainable = sum(p.numel() for p in model.parameters() if p.requires_grad) print("Trainable parameters: {:,}".format(trainable))
모드 전환: train() vs eval()
비유: 축구 선수가 연습(train)할 때와 시합(eval)할 때 행동이 다르듯, 모델도 학습할 때와 평가할 때 동작이 달라야 합니다.
python# 학습 모드: Dropout 활성화, BatchNorm이 배치 통계 사용 model.train() # 평가 모드: Dropout 비활성화, BatchNorm이 저장된 통계 사용 model.eval() # 평가 시에는 그래디언트 계산도 꺼서 메모리와 속도를 절약 with torch.no_grad(): output = model(test_data)
| 동작 | train() 모드 | eval() 모드 |
|---|---|---|
| Dropout | 뉴런 무작위 비활성화 | 모든 뉴런 활성화 |
| BatchNorm | 현재 배치의 평균/분산 사용 | 학습 중 누적된 평균/분산 사용 |
| 용도 | 학습 루프 | 검증/테스트/추론 |
실전 예제: 재사용 가능한 블록 설계
큰 모델은 반복되는 패턴을 "블록"으로 만들어 재사용하면 코드가 깔끔해집니다.
pythonclass ConvBlock(nn.Module): """Conv -> BatchNorm -> ReLU 패턴을 하나의 블록으로""" def __init__(self, in_ch, out_ch, kernel_size=3, padding=1): super().__init__() self.conv = nn.Conv2d(in_ch, out_ch, kernel_size, padding=padding) self.bn = nn.BatchNorm2d(out_ch) self.relu = nn.ReLU() def forward(self, x): return self.relu(self.bn(self.conv(x))) class MyNet(nn.Module): """ConvBlock을 재사용해서 모델 구성""" def __init__(self, num_classes=10): super().__init__() self.block1 = ConvBlock(3, 32) # 3채널 -> 32채널 self.block2 = ConvBlock(32, 64) # 32채널 -> 64채널 self.block3 = ConvBlock(64, 128) # 64채널 -> 128채널 self.pool = nn.MaxPool2d(2) self.fc = nn.Linear(128 * 4 * 4, num_classes) def forward(self, x): x = self.pool(self.block1(x)) # 32x32 -> 16x16 x = self.pool(self.block2(x)) # 16x16 -> 8x8 x = self.pool(self.block3(x)) # 8x8 -> 4x4 x = x.view(x.size(0), -1) # Flatten return self.fc(x) model = MyNet(num_classes=10)
블록 기반 설계의 장점:
- •코드 중복 제거: 같은 패턴을 반복 작성하지 않아도 됩니다
- •수정 용이: 블록 하나만 수정하면 모든 곳에 반영됩니다
- •가독성 향상: 모델 구조를 한눈에 파악할 수 있습니다
핵심 요약
| 개념 | 설명 | 비유 |
|---|---|---|
| nn.Module | 모든 레이어/모델의 기본 클래스 | 레고의 기본 브릭 |
| init | 레이어 정의 | 부품 목록 |
| forward | 데이터 흐름 정의 | 조립 설명서 |
| parameters() | 학습 가능한 가중치 반환 | 조절 가능한 나사들 |
| train()/eval() | 모드 전환 | 연습/시합 모드 전환 |
| nn.Sequential | 순차적 레이어 묶음 | 파이프라인 |
학습 체크리스트
- • super().init()을 호출해야 하는 이유를 안다
- • __init__과 forward의 역할 차이를 설명할 수 있다
- • nn.Sequential과 nn.Module 상속의 차이를 안다
- • model.parameters()로 파라미터를 확인할 수 있다
- • train()과 eval() 모드에서 Dropout/BatchNorm의 동작 차이를 안다
- • 블록 기반 모델 설계의 장점을 이해한다
레슨 정보
- 레벨
- Level 4: 실전 프로젝트
- 예상 소요 시간
- 약 4분
- 참고 영상
- YouTube 링크
💡실습 환경 안내
이 레벨은 PyTorch/GPU가 필요하여 Google Colab 사용을 권장합니다.
Colab은 무료 GPU를 제공하여 PyTorch, CNN, Transformer 등을 실행할 수 있습니다.