새로운 API의 첫 번째 내부 통합
요약
GSoC 프로젝트의 일환으로 sbi 라이브러리에 새로운 빌더 클래스를 도입하고 NPE 트레이너와 통합하는 과정을 다룹니다. 설계 과정에서 시기상조인 추상화를 피하고 소프트웨어 아키텍처를 단순화하며 효율적인 구현을 달성한 경험을 공유합니다.
핵심 포인트
- DensityEstimatorBuilder 도입을 통한 신경망 생성 프로세스 강화
- 데이터 클래스를 활용한 모델 아키텍처의 조기 검증 구현
- 시기상조인 추상화(BuildContext)를 지양하고 설계를 단순화한 사례
- 빌더 패턴을 통한 NPE 트레이너와의 성공적인 통합
다시 만나서 반갑습니다! GSoC 코딩 기간이 한창 진행 중이며, 3주 차와 4주 차는 진전 사항으로 정말 가득 찼습니다.
첫 번째 PR에서 수행한 기초 작업을 이어받아, 저는 방금 sbi의 gsoc-2026 브랜치에 두 개의 주요 PR (#1877 및 #1882)을 성공적으로 병합했습니다. 이 PR들은 첫 번째 구체적인 빌더 클래스 (builder class)를 도입하고 이를 핵심 신경 후험 추정 (Neural Posterior Estimation (NPE)) 트레이너에 직접 연결합니다.
제가 무엇을 만들었는지, 기술적 장애물은 무엇이었는지, 그리고 그 과정에서 배운 소프트웨어 아키텍처 (software architecture)에 관한 매우 가치 있는 교훈에 대해 자세히 살펴보겠습니다.
PR #1877: DensityEstimatorBuilder
이 단계의 주요 목표는 기존의 불투명한 posterior_nn() 및 likelihood_nn() 팩토리 클로저 (factory closures)를 타입이 지정되고, 검사 가능하며, 훨씬 더 견고한 것으로 교체하는 것이었습니다.
이를 해결하기 위해 저는 DensityEstimatorBuilder를 도입했습니다. 이는 1주 차에 수립한 기본 계약을 상속하며, sbi에서 신경망 (neural networks)을 생성하기 위한 통합 진입점 역할을 합니다. Python 데이터 클래스 (dataclasses)의 __post_init__ 메서드를 사용하여, 사용자가 알 수 없는 아키텍처를 제공할 경우 즉시 _VALID_DENSITY_MODELS 세트와 모델 이름을 대조하여 검증하고 조기에 실패 처리합니다.
주요 아키텍처 변화
처음에는 build() 메서드가 BuildContext 객체를 받도록 계획했습니다. 이 컨텍스트 (context)가 사전 계산된 z-scoring 통계치를 포함하여 필요한 모든 정보를 보유하고, 이를 체인을 따라 깔끔하게 전달할 것이라는 아이디어였습니다.
하지만 제가 build() 메서드의 본문을 구현하면서, 저의 멘토인 Jan Teusen은 context 파라미터가 실제로 사용되지 않고 있다는 점을 발견했습니다. 빌더에 필요한 모든 정보는 원시 batch_theta 및 batch_x 텐서 (tensors)로부터 직접 유도될 수 있었습니다.
우리는 BuildContext를 이 시그니처 (signature)에 강제하는 것이 **시기상조인 추상화 (premature abstraction)**였다는 것을 깨달았습니다.
단순히 원래 계획이었다는 이유만으로 설계를 고수하는 대신, z-scoring 통계량이 이후 단계에서 실제로 사전 계산(pre-computed)될 때까지 context 객체 전달을 완전히 연기하기로 결정했습니다. 우리는 로드맵을 업데이트하고 build 시그니처 (signature)를 단순화했습니다.
def build(self, batch_theta: Tensor, batch_x: Tensor):
pass
너비 우선 (breadth first)으로 작업한 다음 깊이 (depth)를 향해 나아가기 위해 context 구현을 연기한 것은 탁월한 결정이었습니다.
PR #1882: Builder를 NPE Trainer에 통합
Builder가 병합됨에 따라, 다음 단계는 통합이었습니다. 저는 PosteriorEstimatorTrainer, NPE_B, NPE_C, 그리고 MNPE가 문자열(strings)이나 호출 가능한 객체(callables)에만 의존하는 대신, 새로운 DensityEstimatorBuilder를 수용하도록 업데이트했습니다.
API를 발전시키면서도 하위 호환성 (backward compatibility)을 유지하기 위해, 점진적인 폐기 (graceful deprecation) 경로를 구현했습니다. 사용자가 문자열(예: "maf")을 전달하면 코드는 여전히 완벽하게 작동하지만, 이제는 FutureWarning을 발생시킵니다.
if density_estimator is None:
self._build_neural_net = self._wrap_builder(DensityEstimatorBuilder(model="maf"))
elif isinstance(density_estimator, str):
...
내 작업에 대한 전반적인 피드백
PR #1882에 대한 코드 리뷰 (code review)는 치열했지만 믿을 수 없을 정도로 보람찼습니다. 저의 멘토는 단순히 간결할 뿐만 아니라, 의도가 강력하고 명시적인 테스트를 작성하는 방법에 대해 피드백을 주었습니다.
예를 들어, 저는 원래 호출 가능한 객체 (callable)를 전달하는 것이 폐기 경고 (deprecation warning)를 트리거하지 않는지 확인하는 테스트를 작성했습니다. 하지만 저는 실제로 경고가 발생하지 않았음을 단언 (asserting)한 것이 아니라, 그저 코드를 실행하고 경고가 없으면 성공이라고 가정했을 뿐이었습니다.
멘토는 warnings.catch_warnings()를 엄격한 필터와 함께 사용하여, 만약 FutureWarning이 새어 나오면 즉시 테스트를 실패하게 만드는 방법을 보여주었습니다:
import warnings
with warnings.catch_warnings():
...
또한 올바른 타입 힌팅 (type hinting) 및 기본 인자 (default arguments) 관리법에 대해서도 심도 있게 살펴보았습니다. 처음에는 NPE_C 초기화 시 density_estimator="maf"를 기본 인자로 남겨두었습니다. 하지만 저의 멘토는 사용자가 인자 없이 클래스를 초기화할 때마다 매번 지원 중단 경고 (deprecation warning)가 발생할 것이라고 지적했습니다! 해결 방법은 타입 힌트의 기본값을 None으로 변경하고, 로직 블록 내부에서 `
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기