# configs/config.yaml
defaults:
- data: ravdess
- model: wav2vec
- _self_
paths:
data_dir: ${oc.env:PROJECT_ROOT}/data
checkpoints_dir: ${oc.env:PROJECT_ROOT}/checkpoints
training:
seed: 42
batch_size: 32
num_workers: 4
max_epochs: 10
logger:
wandb:
project: "multimodal-emotion"
entity: null # your wandb username
# configs/data/ravdess.yaml
dataset:
name: "ravdess"
urls:
small: "https://www.kaggleusercontent.com/datasets/uwrfkaggler/ravdess-emotional-speech-audio.zip"
full: "https://zenodo.org/api/records/1188976/files-archive"
audio:
sample_rate: 16000
duration: 5.0
transcribe:
enable: false
model: "openai/whisper-base"
# src/data/base.py
from abc import ABC, abstractmethod
from typing import Dict, Any
import torch.utils.data as data
class BaseDataset(data.Dataset, ABC):
def __init__(self, config: Dict[str, Any]):
self.config = config
@abstractmethod
def prepare_data(self):
"""Download and prepare dataset if necessary"""
pass
@abstractmethod
def __len__(self):
pass
@abstractmethod
def __getitem__(self, idx):
pass
# src/data/ravdess.py
from typing import Dict, Any
import torch
from .base import BaseDataset
class RavdessDataset(BaseDataset):
def __init__(self, config: Dict[str, Any]):
super().__init__(config)
self.data_dir = config.paths.data_dir
self.prepare_data()
def prepare_data(self):
"""Download and prepare RAVDESS dataset"""
if not self.data_dir.exists():
# Download and extract dataset
# Prepare metadata
pass
def __len__(self):
return len(self.metadata)
def __getitem__(self, idx):
# Load and preprocess audio file
# Return audio tensor and label
pass
# src/model/factory.py
from typing import Dict, Any
from .audio import Wav2VecModel
from .text import RobertaModel
from .fusion import FusionModel
class ModelFactory:
@staticmethod
def create_model(config: Dict[str, Any]):
model_name = config.model.name
if model_name == "wav2vec":
return Wav2VecModel(config)
elif model_name == "roberta":
return RobertaModel(config)
elif model_name == "fusion":
return FusionModel(config)
else:
raise ValueError(f"Unknown model: {model_name}")
# src/model/base.py
import pytorch_lightning as pl
from abc import abstractmethod
from typing import Dict, Any
class BaseModel(pl.LightningModule):
def __init__(self, config: Dict[str, Any]):
super().__init__()
self.config = config
self.save_hyperparameters()
@abstractmethod
def forward(self, x):
pass
@abstractmethod
def training_step(self, batch, batch_idx):
pass
@abstractmethod
def configure_optimizers(self):
pass
def get_embeddings(self, x):
"""Get intermediate embeddings for fusion"""
raise NotImplementedError
# src/trainer.py
import pytorch_lightning as pl
from pytorch_lightning.loggers import WandbLogger
from .data import RavdessDataset
from .model import ModelFactory
def train(config):
pl.seed_everything(config.training.seed)
# Initialize dataset and dataloader
dataset = RavdessDataset(config)
dataloader = torch.utils.data.DataLoader(
dataset,
batch_size=config.training.batch_size,
num_workers=config.training.num_workers
)
# Initialize model
model = ModelFactory.create_model(config)
# Setup logger
logger = WandbLogger(**config.logger.wandb)
# Initialize trainer
trainer = pl.Trainer(
max_epochs=config.training.max_epochs,
logger=logger,
# Add other training configurations
)
# Train model
trainer.fit(model, dataloader)
https://news.hada.io/topic?id=16575
단일 책임 원칙(Single Responsibility Principle)
클래스 분리 및 책임 명확화:
# 1. TextProcessor 클래스 분리
class TextProcessor:
@staticmethod
def clean_text(text: str) -> str:
return re.sub(r'\s+', ' ', text).strip()
@staticmethod
def split_into_chunks(text: str, max_length: int) -> List[str]:
# Utils.split_text_into_chunks 메서드를 여기로 이동
pass
# 2. NotionBlockBuilder 클래스 분리
class NotionBlockBuilder:
@staticmethod
def create_text_block(content: str, block_type: str = "paragraph", keywords: List[str] = None) -> Dict:
pass
@staticmethod
def create_bulleted_list_item(content: str, keywords: List[str] = None) -> Dict:
pass
인터페이스 정의 및 추상화:
from abc import ABC, abstractmethod
class ContentFetcher(ABC):
@abstractmethod
def fetch_items(self, **kwargs):
pass
@abstractmethod
def get_content(self, item):
pass
class ContentProcessor(ABC):
@abstractmethod
def process_content(self, content: str) -> Dict:
pass
설정 관리 개선:
from dataclasses import dataclass
@dataclass
class SummarizerConfig:
max_chunks_per_chapter: int
gpt_model: str
temperature: float
output_language: str
enable_chapters: bool
include_keywords: bool
에러 처리 및 로깅 개선:
class SummarizerException(Exception):
pass
class ContentProcessingError(SummarizerException):
pass
def process_with_error_handling(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
logger.error(f"Error in {func.__name__}: {str(e)}")
raise ContentProcessingError(f"처리 중 오류 발생: {str(e)}")
return wrapper
테스트 용이성 개선:
class MockContentFetcher(ContentFetcher):
def __init__(self, mock_data: Dict):
self.mock_data = mock_data
def fetch_items(self, **kwargs):
return self.mock_data.get('items', [])
def get_content(self, item):
return self.mock_data.get('content', {})
유틸리티 함수 모듈화:
# utils/text.py
def clean_text(text: str) -> str:
pass
# utils/file.py
def save_file(data, filepath: str) -> None:
pass
# utils/notion.py
def create_notion_block(content: str, block_type: str) -> Dict:
pass
7. 의존성 주입 패턴 적용:
class Summarizer:
def __init__(self,
content_fetcher: ContentFetcher,
content_processor: ContentProcessor,
config: SummarizerConfig):
self.fetcher = content_fetcher
self.processor = content_processor
self.config = config