NAS는 딥러닝 모델의 구조(architecture)를 자동으로 설계하는 방법입니다.
사람이 직접 레이어 구성이나 파라미터를 하나하나 고르는 대신, 컴퓨터가 스스로 좋은 구조를 찾아주는 방법이에요.
NAS는 보통 3가지 핵심 요소로 구성됩니다:
요소 | 설명 |
---|---|
Search Space (탐색 공간) | 어떤 구조를 조합해서 만들 수 있는지 정의 (예: Conv, Pooling, skip connection 등) |
Search Strategy (탐색 전략) | 어떤 방식으로 좋은 구조를 찾을지 결정 (진화 알고리즘, 강화학습, 그리드 탐색 등) |
Evaluation Strategy (평가 전략) | 후보 구조의 성능을 어떻게 평가할지 (정확도, FLOPs, latency 등) |
강화학습 기반 NAS
→ controller가 구조를 설계하고, 성능 좋으면 보상 줌
진화 알고리즘 기반 NAS
→ 유전자처럼 구조를 교배, 변이시켜 점점 더 나은 구조 찾음
Differentiable NAS (DARTS) ← 논문에서도 사용
→ 모든 후보 구조를 동시에 학습하면서 연속적인 가중치로 최적의 구조를 선택
→ 속도가 매우 빠르고 효율적
장점 | 단점 |
---|---|
- 구조 설계 자동화 - 정확도 vs 속도 균형 조절 - 새로운 구조 발견 가능 | - 계산량이 많을 수 있음 - 탐색 결과가 데이터셋에 의존할 수 있음 |
"Differentiable NAS를 사용하는 구조라면, 특정 데이터셋으로 학습할 때 NAS가 자동으로 함께 진행되고, 그에 따라 네트워크 구조와 가중치가 동시에 달라집니다."
보통은 구조가 고정되어 있고, 학습 시에는 가중치(weight)만 업데이트하죠.
이 방식은 다음을 동시에 학습합니다:
항목 | 설명 |
---|---|
가중치 () | 각 연산 모듈(예: Conv, Pooling 등)의 일반적인 학습 파라미터 |
구조 선택 가중치 () | 어떤 연산을 선택할지를 결정하는 가중치 (Softmax로 확률처럼 처리) |
즉, 학습하는 동안 "구조(architecture)"도 진화하고, "연산 가중치"도 학습돼요.
맞습니다.
보통 Differentiable NAS에서는 이런 흐름이에요:
탐색(학습) 단계:
구조 가중치 와 일반 가중치 를 함께 학습
구조 확정:
가장 확률이 높은 연산들을 골라 최종 구조로 고정
고정된 구조에서 다시 학습 (선택적 단계):
구조가 확정되었으니, 이제 이 구조로 만 다시 정식 학습해서 성능을 극대화함
질문 | 답변 |
---|---|
학습하면 NAS가 자동 진행되나요? | Differentiable NAS를 사용한다면 그렇습니다. |
그럼 가중치가 달라지나요? | 네, 구조와 함께 가중치도 달라집니다. |
구조는 언제 고정되나요? | 탐색이 끝나고 가장 좋은 구조가 선택된 후에 고정됩니다. |
import torch
import torch.nn as nn
import torch.nn.functional as F
# 연산 후보 정의
class MixedOp(nn.Module):
def __init__(self, C):
super().__init__()
self.ops = nn.ModuleList([
nn.Conv2d(C, C, 3, padding=1, bias=False),
nn.Conv2d(C, C, 5, padding=2, bias=False),
nn.Identity()
])
# 구조 선택 가중치 (학습됨)
self.alpha = nn.Parameter(torch.randn(len(self.ops)))
def forward(self, x):
weights = F.softmax(self.alpha, dim=0) # softmax로 연산 선택 확률 계산
return sum(w * op(x) for w, op in zip(weights, self.ops))
# 간단한 네트워크 (2개의 MixedOp layer)
class TinyNASNet(nn.Module):
def __init__(self, C=16):
super().__init__()
self.stem = nn.Conv2d(3, C, 3, padding=1)
self.layer1 = MixedOp(C)
self.layer2 = MixedOp(C)
self.classifier = nn.Linear(C * 32 * 32, 10)
def forward(self, x):
x = self.stem(x)
x = self.layer1(x)
x = self.layer2(x)
x = x.view(x.size(0), -1)
return self.classifier(x)
# 예시 학습 루프 (MNIST 형태 데이터라고 가정)
model = TinyNASNet()
optimizer = torch.optim.Adam(model.parameters(), lr=0.003)
criterion = nn.CrossEntropyLoss()
# dummy data (3채널 이미지, 32x32)
x = torch.randn(8, 3, 32, 32)
y = torch.randint(0, 10, (8,))
# 학습 단계
model.train()
for epoch in range(10):
optimizer.zero_grad()
out = model(x)
loss = criterion(out, y)
loss.backward()
optimizer.step()
print(f"Epoch {epoch}: loss = {loss.item():.4f}")
학습이 끝나면, 각 MixedOp의 alpha
값을 확인하면 어떤 연산이 선택되었는지 알 수 있어요.
print("Layer1 alpha:", F.softmax(model.layer1.alpha, dim=0).data)
print("Layer2 alpha:", F.softmax(model.layer2.alpha, dim=0).data)
alpha
는 학습 가능한 파라미터입니다. 각 후보 연산마다 하나씩 존재합니다.
예: Conv3x3
, Conv5x5
, Skip
, None
등
소프트맥스(softmax(alpha))를 통해 연산들의 가중치(weighted sum)로 계산된 출력을 사용합니다.
즉, 여러 연산이 동시에 계산되지만, 실제 사용은 거의 한두 개에 집중됩니다.
학습이 진행되면서 덜 중요한 연산은 alpha 값이 작아지고,
결국에는 softmax 후 거의 0이 되어 영향력이 사라집니다.
NAS 탐색이 끝나면:
Input
|
------------------------
| | | | |
Conv3 Conv5 Skip None ...
| | | |
v v v v
w1*x1 + w2*x2 + w3*x3 + w4*x4 ← softmax(alpha)
x_out = sum(p_o * o(x) for o in operations)
o(x)
이 실제로 전부 실행됨탐색 후에는 softmax에서 확률이 가장 높은 하나만 선택해서
불필요한 연산은 제거합니다:
chosen_op = argmax(α)
x_out = chosen_op(x)
이걸 "discretization" 또는 "architecture finalization"이라고 합니다.
즉: