같은 기능을 하는 함수가 생각보다 많은 것 같다. 헷갈리지 않도록 조심해야지!
학습시간 09:00~02:00(당일17H/누적428H)
| 항목 | MLP | LeNet | CNN |
|---|---|---|---|
| 기본 아이디어 | 모든 입력을 일렬로 펼쳐서 처리 (Flatten + Linear) | 이미지의 공간 정보를 살리기 위해 Convolution 사용 | 깊고 복잡한 convolution 블록으로 특징 추출 |
| 입력 형태 | (B, D) / (B, C×H×W) | (B, 1, 28, 28) | (B, 1~3, H, W) |
| 주요 레이어 | Linear, ReLU, Dropout | Conv2d, AvgPool, Sigmoid, FC | Conv2d, MaxPool, BatchNorm, ReLU, FC, Dropout |
| 반복 구조 | 없음 (Dense만 연속) | Conv → Pool 블록 2번 | Conv → BN → ReLU → Pool 블록 수십 번까지 가능 |
| 공간 구조 인식 | 없음 (Flatten해서 공간감 사라짐) | 약함 (Conv 있음) | 매우 강함 (멀티스케일 특징 인식) |
| 비선형성 처리 | ReLU (or LeakyReLU) | Sigmoid | ReLU, LeakyReLU, GELU 등 다양 |
| 정규화 | 없음 | 없음 | BatchNorm, LayerNorm 등 사용 |
| 출력 구조 | Linear → Softmax | FC 3단 구성 | FC or Global AvgPool + Linear |
| 주요 용도 | 수치형, 표형 데이터 (CSV) | 숫자 이미지 (MNIST 등) | 이미지 전반 (사진, 영상 등) |
| 모델 깊이 | 얕음 (3~4층) | 얕음 (5~6층) | 깊음 (수십~수백층) |
| 파라미터 수 | 많음 (입력 차원에 따라 폭발) | 적음 (conv로 파라미터 감소) | 상황에 따라 다양 (최적화 가능) |
| 현대성 | 고전적 | CNN 시초 | 실전 표준 모델 |
아래 3개 코드는 정확히 동일한 기능을 한다.
ToTensor() 함수는 다음 릴리즈에 삭제 예정이라고 함.
transforms 보다 v2가 짧으니 애용해야겠다.
# 1.
transforms = v2.Compose([
v2.ToTensor()
])
# 2.
transforms = v2.Compose([
v2.ToImage(),
v2.ToDtype(torch.float32, scale=True)
])
# 3.
transforms = transforms.Compose([
transforms.ToImage(),
transforms.ToDtype(torch.float32, scale=True)
])
아래 두 코드는 정확히 같은 기능을 한다.
1번은 라이브러리 데이터 불러오기, 2번은 로컬 데이터 불러오기.
# 1.
transforms = v2.Compose([
v2.Resize((224, 224)),
v2.ToImage(),
v2.ToDtype(torch.float32, scale=True),
v2.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.2470, 0.2435, 0.2616]),
])
train_dataset = datasets.CIFAR10(
root='./images',
train=True,
download=True,
transform=transforms
)
test_dataset = datasets.CIFAR10(
root='./images',
train=False,
download=True,
transform=transforms
)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
print(f'▶ Train set: {len(train_dataset)} | Test set: {len(test_dataset)}')
print(next(iter(train_loader))[0].shape)
# 2.
transforms = v2.Compose([
v2.Resize((224, 224)),
v2.ToImage(),
v2.ToDtype(torch.float32, scale=True),
v2.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.2470, 0.2435, 0.2616]),
])
dataset = datasets.ImageFolder(root='./images', transform=transforms)
train_size = int(len(dataset) * 0.8)
test_size = len(dataset)-train_size
train_dataset, test_dataset = random_split(dataset, [train_size, test_size])
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
print(f'▶ Train set: {len(train_dataset)} | Test set: {len(test_dataset)}')
print(next(iter(train_loader))[0].shape)
아래 mean & std는 0.5로 넣어도 문제는 없지만, 좋은 성능을 위해선 직접 구해줄 필요가 있다.
torch.stack([train_dataset[i][0] for i in range(len(train_dataset))]) 이걸로 한 줄이면 끝!
transforms = v2.Compose([
v2.ToImage(),
v2.ToDtype(torch.float32, scale=True),
v2.Normalize(mean=[0.4860, 0.4661, 0.4140], std=[0.2569, 0.2502, 0.2622]),
])
지금은 시퀀셜 써도 무방할 것 같은데 제어문이 안 먹힌다고 한다. 결국 수동에 익숙해지는 수밖에 없는 것 같다.
| 항목 | Sequential | Manual |
|---|---|---|
| 선언 방식 | 레이어들을 한 줄로 묶음 | 개별적으로 선언 |
| 커스터마이징 | 제한적 | 자유롭게 조건문, skip 연결 가능 |
| 추천 상황 | 구조 단순할 때 | 복잡한 흐름, 다양한 분기 처리할 때 |
아래 두 코드는 정확히 같은 기능을 한다.
# 1. 시퀀셜 O
class CNN_Sequential(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 16, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(16, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(32 * 56 * 56, 10)
)
def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x
# 2. 시퀀셜 X
class CNN_Manual(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
self.fc = nn.Linear(32 * 56 * 56, 10)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.max_pool2d(x, 2)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
이제 시작이구나 무서운 미션...