Regime Sequence Dataset Builder Refactor
🗓️ 11/19/2025 👤 Role: Machine Learning Engineer
PyTorchPolarsIncrementalPCAMiniBatchKMeansDataPipeline
Problem & Condition
배경
- 주식(1d) / 크립토(1m)를 동시에 다루는 regime_sequence용 Dataset Builder를 운영 중이었다.
- 각 티커별로:
- 기술지표 피처(30~40개)를 기반으로
- window_size=20짜리 마이크로 패턴 윈도우를 만들고
- PCA → KMeans로 구조적 레짐(micro_cluster_id)을 할당한 뒤
- future return quantile 기반 레이블을 생성하는 파이프라인.
문제 상황
-
Flat windows를 한 번에 생성
- 1분봉 6개월치 BTCUSDT 데이터 기준:
- 약 20만 rows * 20(window) * 35(features) ≈ 1.4e8 scalar짜리 행렬이 만들어졌고,
_build_windows호출 시 수백 MB~수 GB 메모리를 한 번에 잡아먹었다.
- PCA fit은 이 huge matrix에 대해 한 번에 수행되어, 수 분~수십 분이 걸리거나 메모리 한계에 부딪혔다.
- 1분봉 6개월치 BTCUSDT 데이터 기준:
-
PCA/KMeans를 티커마다 재학습
- 기존
run_regime_pipeline흐름:StandardScaler.fitPCA.fit_transformKMeans.fit_predict- 를 티커마다 새로 수행했다.
- 주식 일봉처럼 row 수가 적을 땐 버틸 수 있었지만, 크립토 1분봉에서는 티커 수 * 기간에 비례해 연산량이 기하급수적으로 증가하여 BTC 6개월 처리에만 10분 이상이 걸리는 비현실적인 상황이 발생했다.
- 기존
-
로딩/fit 구간을 나누지 못함
- parquet 로딩 시:
- 전체 기간을 한 번에 읽고
- 이후에 date filter로 train/val/test split을 자르는 구조였다.
- batch 개념이 없어:
- 필요 없는 기간까지 디스크에서 읽어 오고
- 메모리 점유도 불필요하게 커졌다.
- parquet 로딩 시:
제약 조건
- window_size=20은 실험 프로토콜 상 고정 (바꿀 수 없음).
- technical feature 구성은 가능하면 유지해야 했고,
- 레짐 구조(PCA + KMeans)를 과도하게 단순화시키지 않으면서도 크립토 1분봉 6개월+ 규모를 안정적으로 처리해야 했다.
Candidates
1. 기존 파이프라인 유지 + 하위샘플링
- 아이디어
- 기존
run_regime_pipeline을 그대로 두고, - 크립토에서는 row 수를 줄이기 위해:
- 일정 비율 random sampling
- 또는 시간간격 upsampling(예: 1m → 5m)으로 데이터를 줄이는 전략.
- 기존
- 장점
- 구현이 가장 간단하다.
- 기존 코드와 분석 결과를 거의 손대지 않고 사용할 수 있다.
- 단점 / Trade-off
- 미시구조 정보 손실: 암호화폐의 초단기 패턴이 핵심인데, 하위샘플링 시 이 정보를 잃을 가능성이 크다.
- 데이터 수가 줄어들면 레짐 분할의 신뢰도도 떨어진다.
- “1분봉 미시구조를 보기 위해 실험한다”는 문제 정의와 충돌.
2. PCA 차원/feature 축소만으로 해결
- 아이디어
- window_size=20은 유지하되,
- PCA 입력 차원 축소:
- 기술지표 35개 → 15~20개
- PCA 컴포넌트 수 40 → 10~20개
- 즉, feature_dim을 줄여서 matrix 크기를 줄이는 방법.
- 장점
- flat windows 행렬 크기 자체를 줄여서 PCA 연산량을 상당 부분 감소시킬 수 있다.
- 모델 구조는 유지되므로 downstream 실험에 영향이 적다.
- 단점 / Trade-off
- row 수(시점 수)가 많다는 문제 자체는 해결되지 않는다.
- 여전히 “전체 flat windows를 한 번에 생성”하는 구조라, 데이터가 더 늘어나면 같은 문제가 다시 발생한다.
- 근본적으로는 one-shot fit 구조를 유지하고 있어 스케일링 한계가 뚜렷하다.
3. Incremental Flow + Model Cache + Batched IO (채택)
-
아이디어
- IncrementalPCA + MiniBatchKMeans 기반의 완전한 incremental 파이프라인으로 전환.
_build_windows호출도 전체가 아니라 배치 단위로 수행하고,partial_fit을 사용해 streaming 학습.PipelineCache를 이용해 첫 티커에서 학습한 scaler/PCA/KMeans를 저장하고, 이후 티커에서는transform + predict만 수행.- parquet 로딩도
dataset_config의 split 날짜 범위를 합친 범위만 읽고, universe를--batch-size단위로 나눠서 ETL.
-
장점
- 데이터가 커져도 메모리 사용량이 데이터 크기에 선형적으로만 증가하고, 개별 배치 크기 기준으로 연산량이 제한된다.
- PCA/KMeans를 시장 단위로 단 한 번만 학습할 수 있어서 전체 처리 시간이 티커 수에 선형에 가깝게 변한다.
- train/val/test 밖의 불필요한 기간을 읽지 않아 I/O 비용이 줄어든다.
-
단점 / Trade-off
- 구현 복잡도가 높고, 기존
run_regime_pipeline과 별도 코드 경로(_run_crypto_incremental_pipeline)를 관리해야 한다. - IncrementalPCA의 설명분산비가 기존 PCA와 약간 다를 수 있고, MiniBatchKMeans의 결과도 batch 순서에 따라 미세한 차이가 발생할 수 있다.
- 디버깅을 위해 로깅과 검증 코드가 더 많이 필요하다.
- 구현 복잡도가 높고, 기존
Decision
최종 선택
Candidate 3: Incremental Flow + Model Cache + Batched IO
(IncrementalPCA + MiniBatchKMeans + PipelineCache + Split-aware ETL)
근거
-
스케일링 관점에서 유일하게 오래 버틸 수 있는 구조
- flat windows를 전체 한 번에 만들지 않고,
_iter_flat_window_batches같은 helper를 두어 batch 단위로 window를 생성하고 처리한다. - IncrementalPCA / MiniBatchKMeans는 본질적으로 “streaming 알고리즘”이라 데이터가 2배, 5배 늘어나도 의미있는 시간/메모리 범위에서 따라갈 수 있다.
- flat windows를 전체 한 번에 만들지 않고,
-
도메인 가정을 유지할 수 있음
- window_size=20, 기술지표 세트, macro/micro regime 정의 등
실험 설계의 핵심 가정들을 바꾸지 않아도 된다. - 단지 “어떻게 fit하느냐”의 문제만 바꿔서 시스템적인 병목을 제거했다.
- window_size=20, 기술지표 세트, macro/micro regime 정의 등
-
시장 단위 표현 학습이라는 해석 상의 장점
- 첫 티커(BTCUSDT 등)에서 fit한 PCA/KMeans가 전체 마켓(ETH, SOL, XRP 등)에 공통적으로 쓰이는 구조다.
- 이는 “단일 시장 마이크로 구조 표현”이라는 해석을 가능하게 해주고,
이후 cross-asset 실험(자산 간 패턴 보편성 평가)에도 자연스럽게 연결된다.
-
엔지니어링 관점에서 재사용성 증가
PipelineCache구조를 도입함으로써:- 연구용 스크립트,
- offline batch pipeline,
- online inference prototype 에서 모두 같은 scaler/PCA/KMeans를 재사용할 수 있다.
Effects
1. 성능/시간 개선
-
Before
- BTC 6개월치 1분봉 기준
_build_windows+ PCA.fit + KMeans.fit 동안 10분 이상 소요.
- 티커 수가 늘어날수록 거의 “티커 수 * 10분” 수준으로 늘어났다.
- 메모리 사용량도 flat windows 때문에 수 GB까지 치솟는 일이 있었다.
- BTC 6개월치 1분봉 기준
-
After
- IncrementalPCA + MiniBatchKMeans + batch windows 도입 후:
- 동일 데이터에서 수십 초 내외로 regime pipeline 종료.
- “BTCUSDT: train=260,622 / val=84,941 / test=86,401” 수준의 대형 시퀀스 데이터셋도 안정적으로 처리.
- 다른 티커(ETHUSDT, SOLUSDT, XRPUSDT)는
- PipelineCache를 통해 fit 없이 transform만 수행하므로
- 각 티커당 처리 시간이 크게 줄어들었다.
- IncrementalPCA + MiniBatchKMeans + batch windows 도입 후:
2. 메모리 사용량 안정화
- flat windows를 한 번에 만들던 구조에서:
- 한 번에 수백 MB~수 GB 배열을 생성 → OOM 위험 존재.
- batch 기반 incremental 구조로 전환한 뒤:
- batch_size 단위로만 window를 만들고 폐기하기 때문에
- 메모리 사용량이 일정 수준 이하로 유지된다.
- 큰 parquet 파일을 읽을 때도 split 범위만 읽기 때문에
전체 파이프라인 메모리 풋프린트가 예측 가능해졌다.
3. 데이터 품질/표현력 유지
- window_size, 기술지표, 레짐 정의 등은 유지했기 때문에:
- 모델이 보는 정보량은 그대로이고,
- 단지 계산 방식을 incremental하게 나눈 것에 불과하다.
- IncrementalPCA / MiniBatchKMeans 기반 결과와
기존 PCA / KMeans 기반 결과를 샘플 비교했을 때,
- 클러스터 수익률 통계,
- macro regime 분포,
- morphological embedding의 분산비 등에서 본질적인 왜곡은 관찰되지 않았다.
4. 연구 생산성 증가
- 이제 1분봉 6개월치 수준의 데이터도
- “한 번 해보기” 정도의 비용으로 돌려볼 수 있게 되었다.
- 새 자산 추가, 기간 변경, horizon 바꾸기 등의 실험이
- 전에는 반나절~하루 단위였다면,
- 지금은 “스크립트 한 번 돌려보고 다시 수정”하는 사이클이 가능해졌다.
Dataset Builder Refactor Log (Concept Details)
Flat Windows & Memory Explosion
- Flat window란?
- 시계열을 window_size 길이의 조각으로 잘라,
[time t-19 ~ t]구간을 하나의 벡터로 펼친 것. - feature_dim=35, window_size=20이면 한 window는 700차원 벡터가 됨.
- 시계열을 window_size 길이의 조각으로 잘라,
- 문제는,
- row 수가 20만이라면 20만 * 700 = 1.4e8 scalar짜리 행렬이 만들어져,
- 한 번에 PCA에 넣기엔 메모리/시간 모두 부담이 너무 크다는 점이다.
IncrementalPCA
- 일반 PCA
- 모든 데이터를 한 번에 모아서 공분산 행렬을 만들고, 고유값 분해를 한 뒤 주성분을 구하는 방식.
- 데이터가 커지면 공분산 행렬 계산 자체가 병목이 됨.
- IncrementalPCA
- 데이터를 여러 batch로 나눠서:
partial_fit(batch1)partial_fit(batch2)- …
partial_fit(batchN)
- 식으로 통계량을 점진적으로 갱신하는 방식.
- 전체 데이터를 한 번에 메모리에 올리지 않아도 되고, PCA의 근사해를 점진적으로 얻을 수 있다.
- 데이터를 여러 batch로 나눠서:
- 이번 refactor에서:
_iter_flat_window_batches로 window를 batch 단위로 생성하고- IncrementalPCA에
partial_fit으로 넣어 실제 incremental하게 동작하도록 구현했다.
MiniBatchKMeans
- 일반 KMeans
- 전체 데이터에 대해 반복적으로 centroid 업데이트를 수행.
- 데이터가 크면 각 iteration에서의 거리 계산 비용이 커진다.
- MiniBatchKMeans
- 작은 batch 단위로 샘플을 뽑아 centroid를 업데이트하는 방식.
- 메모리 부담이 적고, 대규모 데이터에서 훨씬 빠르며, 통계적으로 괜찮은 근사 해를 준다.
- 이 프로젝트에서는:
- PCA embedding 결과에 대해 MiniBatchKMeans를 사용해 마이크로 레짐을 효율적으로 학습했다.
PipelineCache
- 문제
- 티커마다 scaler/PCA/KMeans를 새로 fit하면, 데이터가 늘어날수록 계산량이 티커 수 * 데이터 크기만큼 증가한다.
- PipelineCache 구조
- 첫 번째 티커에서 fit한 모델들을 저장:
{"scaler": StandardScaler, "pca": IncrementalPCA, "kmeans": MiniBatchKMeans}
- 이후 티커에서는:
_apply_cached_pipeline을 통해transform + predict만 수행.
- 결과:
- 시장 단위 표현(shared representation) 구조를 만들 수 있고,
- 전체 파이프라인 속도도 크게 향상되었다.
- 첫 번째 티커에서 fit한 모델들을 저장:
Split-aware ETL
- 이전에는:
get_etl_data(start=None, end=None)처럼 전체 기간을 불러온 뒤,- 뒤에서 date filter로 train/val/test를 잘랐다.
- 지금은:
dataset_config의 split 설정을 모아서min(train.start, val.start, test.start)~max(train.end, val.end, test.end)범위를 계산하고,
- ETL에서도 이 범위만 로딩하도록 변경했다.
- 이로 인해:
- 필요 없는 과거/미래 구간을 읽지 않게 되고,
- 큰 parquet에 대해서도 안정적으로 동작한다.