콘텐츠로 이동

개발자 가이드

커스텀 컴포넌트 개발 및 Agent 확장 방법

핵심 철학 이해

Why in Key, How in Value

  • Key(목적): Agent가 "왜 이 컴포넌트가 필요한가" (용도/목적)
  • Value(방법): "어떤 구현체로 해결할 것인가" (구체적 방법)
## 단일 스키마: component_type: implementation
model: resnet18       # "모델이 필요하다(Why)" + "ResNet18로(How)"
optimizer: adam       # "최적화가 필요하다(Why)" + "Adam으로(How)"

## 다중 스키마: component_type: {key: implementation}
dataloader:
  train: basic        # "학습용 데이터가 필요하다(Why)" + "basic으로(How)"
  val: basic          # "검증용 데이터가 필요하다(Why)" + "basic으로(How)"

transform:
  weak: "resize | normalize"     # "약한 증강이 필요하다(Why)" + "resize|normalize로(How)"
  strong: "resize | randaugment" # "강한 증강이 필요하다(Why)" + "resize|randaugment로(How)"

다중 스키마 특징

  • 평평한 1계층 구조: 컴포넌트 이름과 파라미터가 모두 동일 레벨
  • Key는 목적: train/val/test, weak/strong 등은 Agent 관점의 용도
  • 기본값: 명시하지 않으면 basic 사용

1. 새로운 컴포넌트 추가

모델 추가

## cvlabkit/component/model/my_model.py
from cvlabkit.component.base import Model
import torch.nn as nn

class MyModel(Model):
    def __init__(self, cfg):
        super().__init__()
        # 필수: namespace 접근 (없으면 AttributeError)
        num_classes = cfg.num_classes

        # 선택적: get() 사용 (기본값 제공)
        hidden_size = cfg.get("hidden_size", 512)
        dropout_p = cfg.get("dropout_p", 0.1)

        self.fc = nn.Linear(784, hidden_size)
        self.dropout = nn.Dropout(dropout_p)
        self.classifier = nn.Linear(hidden_size, num_classes)

    def forward(self, x):
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        x = self.dropout(x)
        return self.classifier(x)

YAML 설정:

model: my_model
num_classes: 10        # 필수 (없으면 AttributeError)
hidden_size: 256       # 선택적 (없으면 512 사용)
dropout_p: 0.2         # 선택적 (없으면 0.1 사용)

기존 라이브러리 래핑

## cvlabkit/component/optimizer/my_adam.py
from cvlabkit.component.base import Optimizer
import torch.optim as optim

class MyAdam(Optimizer):
    def __init__(self, cfg, parameters):
        # 평평한 구조에서 파라미터 가져오기
        lr = cfg.get("lr", 0.001)
        weight_decay = cfg.get("weight_decay", 0)

        # 기존 라이브러리를 내부 객체로 생성
        self.opt = optim.Adam(parameters, lr=lr, weight_decay=weight_decay)

    def step(self):
        # 커스텀 로직 추가 가능
        print(f"Learning rate: {self.opt.param_groups[0]['lr']}")
        self.opt.step()

    # zero_grad() 등은 InterfaceMeta가 자동으로 self.opt로 위임

2. 새로운 Agent 추가

## cvlabkit/agent/my_agent.py
from cvlabkit.core.agent import Agent

class MyAgent(Agent):
    def __init__(self, cfg, component_creator):
        super().__init__(cfg, component_creator)
        # self.create = component_creator 자동 설정됨

    def setup(self):
        # 지휘자 역할: 의존성 순서대로 컴포넌트 생성

        # 1. 독립적인 컴포넌트들 먼저
        self.model = self.create.model()
        self.loss_fn = self.create.loss()

        # 2. 의존성이 있는 컴포넌트는 명시적 주입
        self.optimizer = self.create.optimizer(self.model.parameters())

        # 3. 다중 스키마: Key로 목적 구분
        self.train_loader = self.create.dataloader.train()
        self.val_loader = self.create.dataloader.val()

        # 4. 선택적 컴포넌트 (YAML에 있을 때만)
        if 'scheduler' in self.cfg:
            self.scheduler = self.create.scheduler(self.optimizer)

        # 5. 변환: 용도별로 구분
        if 'transform' in self.cfg:
            self.weak_transform = self.create.transform.weak()
            self.strong_transform = self.create.transform.strong()

    def train_step(self, batch):
        x, y = batch
        pred = self.model(x)
        loss = self.loss_fn(pred, y)

        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

        return {"loss": loss.item()}

3. Config 접근 가이드

기본 원칙

  • 필수 파라미터: cfg.key (AttributeError로 누락 감지)
  • 선택적 파라미터: cfg.get("key", default) (안전한 기본값)
class MyComponent:
    def __init__(self, cfg):
        # 필수: 없으면 즉시 에러
        self.num_classes = cfg.num_classes
        self.model_name = cfg.model_name

        # 선택적: 기본값으로 안전하게
        self.learning_rate = cfg.get("lr", 0.001)
        self.batch_size = cfg.get("batch_size", 32)
        self.use_dropout = cfg.get("use_dropout", True)

        # 조건부 로직
        if cfg.get("use_pretrained", False):
            self.load_pretrained(cfg.pretrained_path)

4. YAML 스키마 가이드

평평한 구조 (모든 파라미터 1계층)

## 컴포넌트와 파라미터가 동일 레벨
model: resnet18
optimizer: adam
loss: cross_entropy

## 모든 파라미터도 1계층에
num_classes: 10
lr: 0.001
batch_size: 32
epochs: 100

## 다중 스키마: Key는 목적
dataloader:
  train: basic(split=train, shuffle=true)
  val: basic(split=val, shuffle=false)
  test: basic                           # 기본값 사용

transform:
  weak: "resize | normalize"
  strong: "resize | randaugment | normalize"

Creator 사용법

## Agent에서의 접근
self.model = self.create.model()                    # 단일 스키마
self.train_loader = self.create.dataloader.train()  # 다중 스키마 - Key로 목적 지정
self.val_loader = self.create.dataloader.val()      # 다중 스키마 - Key로 목적 지정
self.weak_aug = self.create.transform.weak()        # 다중 스키마 - 약한 증강
self.strong_aug = self.create.transform.strong()    # 다중 스키마 - 강한 증강

5. 개발 규칙

DO ✅

  • Config 접근: 필수는 cfg.key, 선택은 cfg.get()
  • 의존성 주입: Agent가 명시적으로 관리 (optimizer(model.parameters()))
  • 다중 스키마: Key로 목적 구분 (train/val, weak/strong)
  • 평평한 구조: 모든 파라미터를 1계층에 배치
  • 기본값: 명시하지 않으면 basic 사용

DON'T ❌

  • cvlabkit/core/ 수정: 안정성 정책 위반
  • 직접 Creator 생성: Agent에서 Creator() 직접 호출 금지
  • 중첩 구조: 파라미터를 여러 계층으로 나누지 말 것
  • 컴포넌트 간 직접 의존: 모든 의존성은 Agent가 관리

6. 테스트 방법

## 설정 검증 (Dry-run)
uv run main.py --config config/my_experiment.yaml

## 실제 실행
uv run main.py --config config/my_experiment.yaml --fast

## 템플릿 생성으로 필요한 모든 파라미터 확인
python config/generate_template.py

7. 자주 사용하는 패턴

조건부 컴포넌트

class MyAgent(Agent):
    def setup(self):
        # 기본 컴포넌트
        self.model = self.create.model()
        self.optimizer = self.create.optimizer(self.model.parameters())

        # 선택적 컴포넌트 (YAML에 있을 때만)
        if 'scheduler' in self.cfg:
            self.scheduler = self.create.scheduler(self.optimizer)

        if 'transform' in self.cfg:
            self.transform = self.create.transform.train()
## 자동으로 모든 조합 실험 생성
run_name: "experiment_lr{{lr}}_bs{{batch_size}}"
lr: [0.001, 0.01, 0.1]        # 3개 값
batch_size: [32, 64, 128]     # 3개 값
## → 총 3×3 = 9개 실험 자동 생성

복잡한 파이프라인

transform:
  train: "resize(size=224) | random_flip | normalize(mean=[0.485, 0.456, 0.406])"
  val: "resize(size=224) | normalize(mean=[0.485, 0.456, 0.406])"
## Agent에서 사용
self.train_transform = self.create.transform.train()  # 파이프라인 자동 구성
self.val_transform = self.create.transform.val()      # 파이프라인 자동 구성

관련 문서