모델
67b630e 기준입니다.Kronos는 과거의 금융 캔들 여러 개를 입력받아 미래 구간의 캔들 수치를 생성하는 사전학습 모델입니다. 핵심은 가격을 곧바로 맞히는 회귀식 대신, 시장의 움직임을 토큰으로 바꿔 다음 토큰을 예측한다는 점입니다. 여기서 토큰은 한국어 단어나 종목명이 아니라, 학습된 숫자 패턴의 식별자입니다.
입력의 기본 단위는 한 시점의 open, high, low, close, volume, amount입니다. 가격 네 개와 거래량·거래대금 두 개를 묶어 시간순으로 나열합니다. 미래 뉴스, 기업 공시, 주문장, 투자자의 자연어 질문을 받는 범용 챗봇 인터페이스는 핵심 모델에 없습니다.
여섯 숫자를 매번 길게 설명하는 대신, 비슷한 움직임에 카드 번호를 붙인다고 생각해 보세요. 토크나이저는 숫자를 카드로 바꾸는 압축기이고, Kronos는 앞 카드들을 보고 뒤 카드를 고르는 이야기 생성기입니다.
마지막에는 선택한 카드를 다시 숫자로 풉니다. 압축에서 정보가 줄고, 생성에서 우연성이 들어갑니다. 따라서 출력은 관측된 미래 사실이 아니라 학습한 패턴을 바탕으로 만든 가능한 경로입니다.
| 객체 | 담당하는 일 | 소스에서 읽을 곳 |
|---|---|---|
KronosTokenizer | 연속 수치 ↔ 두 계층의 정수 토큰 변환 | model/kronos.py의 encode(), decode() |
Kronos | 앞 토큰을 읽고 다음 토큰의 확률 계산 | 같은 파일의 forward(), decode_s1(), decode_s2() |
KronosPredictor | DataFrame 검사·정규화·추론·원래 단위 복원 | 같은 파일의 predict(), predict_batch() |
모델 수식을 모두 외우기보다, 한 DataFrame이 어떤 모양의 텐서와 토큰으로 바뀌는지 설명하는 것을 목표로 삼으세요. 그다음 “학습 때 쓴 정보가 추론 때도 있는가?”를 묻는 단계까지 가면, 다른 시계열 저장소도 훨씬 잘 읽을 수 있습니다.
2026-10-23 분석 대상은 GitHub Trending Python Weekly 13위의 Kronos입니다. 다만 순위와 스타 수만으로 특정 주간의 유입 원인까지 알 수는 없습니다. 여기서는 공개된 연구와 코드가 관심을 끌 만한 이유를 세 가지로 나눠 봅니다.
Kronos 논문은 45개 거래소의 120억 건이 넘는 K-line으로 사전학습했다고 설명합니다. 가격 예측의 RankIC 개선도 보고하지만, 이는 논문의 데이터·비교 모델·실험 조건에 대한 결과입니다. 전체 사전학습 원자료가 이 저장소에 들어 있는 것은 아니므로, 코드만으로 해당 결과 전체를 재현했다고 볼 수 없습니다.
다음 표는 설계 관점의 비교입니다. Chronos는 2024년 원 논문, TimesFM은 2024년 수정 논문의 구조를 기준으로 하며, 각 프로젝트의 모든 후속 버전을 묶어 평가하지 않습니다.
| 접근 | 수치를 다루는 방식 | Kronos에서 더 잘 보이는 학습 지점 |
|---|---|---|
| 마지막 값 유지 | 마지막 관측값을 미래에도 반복하는 단순 기준선 | 토큰 압축과 사전학습이 실제로 추가 가치를 만드는지 비교할 수 있음 |
| 직접 회귀 모델 | LSTM·Transformer 등이 연속적인 미래 수치를 직접 출력하도록 구성 | 연속값 오차와 토큰 분류 오차를 분리하는 두 단계 학습을 관찰 |
| Chronos 원 모델 | 스케일링 후 고정 구간으로 양자화하여 언어 모델로 학습 | 고정 구간 대신 학습된 다변량 압축기와 계층형 토큰을 사용 |
| TimesFM 초기 모델 | 연속 시계열을 패치로 묶어 decoder 기반 모델에 입력 | 시간 묶음과 별개로, 한 캔들의 상태를 이산 토큰으로 표현하는 선택을 비교 |
비교 근거: Chronos 원 논문 소개, TimesFM 초기 논문. 금융 입력에 잘 맞는 설계라는 사실이 모든 데이터에서 더 높은 정확도를 보장하지는 않습니다.
이 저장소의 교육적 가치는 예측선 자체보다, 표현 학습과 생성 모델을 하나의 데이터 파이프라인으로 연결해 볼 수 있다는 데 있습니다.
루트 requirements.txt가 기본 의존성 선언입니다. pyproject.toml로 빌드되는 패키지 구조는 없습니다. README 기준 Python 요구 버전은 3.10 이상이며, 아래 버전은 의존성 파일에 적힌 값입니다.
| 기술 | 실제 선언 | 이 저장소에서 하는 일 |
|---|---|---|
| PyTorch | torch>=2.0.0 | 텐서·자동미분·Attention·분산 학습 |
| NumPy | numpy — 버전 미고정 | 열별 통계·정규화·예측 경로 평균 |
| pandas | pandas와 pandas==2.2.2 중복 | CSV·시간 특성·DataFrame 반환; 고정 선언도 함께 적용됨 |
| einops | einops==0.8.1 | 양자화 손실에서 그룹 단위 텐서 재배열 |
| Hugging Face Hub | huggingface_hub==0.33.1 | PyTorchModelHubMixin으로 설정·가중치 읽기와 저장 |
| Safetensors | safetensors==0.6.2 | Hub 모델 가중치 직렬화 경로 지원 |
| 시각화·진행 표시 | matplotlib==3.9.3, tqdm==4.67.1 | 예측 비교 그림·시간 단계 진행률 |
webui/templates/index.html은 1,238줄의 HTML 한 파일입니다. 인라인 JavaScript 약 600줄, 인라인 CSS 약 430줄이 포함되며 이 수치는 HTML 줄 수와 중복됩니다. React·Vue·TypeScript 빌드가 아니라, Flask가 템플릿을 전달하고 JavaScript가 JSON API를 호출하는 구조입니다.
| 구성 | 실제 버전·경로 | 읽어 볼 연결점 |
|---|---|---|
| Flask | flask==2.3.3 | webui/app.py의 index(), load_model(), predict() |
| CORS 확장 | flask-cors==4.0.0 | CORS(app) 호출로 교차 출처 요청 설정 |
| 차트 | Python plotly==5.17.0 | create_prediction_chart()가 그림을 JSON으로 직렬화 |
| 브라우저 라이브러리 | Plotly의 plotly-latest.min.js, Axios의 버전 없는 CDN 주소 | 템플릿의 Axios API 호출과 Plotly.newPlot(); 브라우저 쪽 버전은 고정되지 않음 |
| Web UI 전용 요구 | torch>=2.1.0, numpy>=1.26.0 | 기본 파일보다 하한이 높음; 출처는 webui/requirements.txt |
finetune/utils/training_utils.py::setup_ddp()는 nccl로 프로세스 그룹을 만들고, RANK·WORLD_SIZE·LOCAL_RANK를 읽습니다. 이는 CUDA 중심 학습 경로입니다.pyqlib, 학습 스크립트에는 comet_ml, CSV 설정에는 yaml, 테스트에는 pytest가 등장합니다. 루트와 Web UI의 의존성 파일에는 이 패키지들이 선언되어 있지 않습니다.load_data_file()에 pd.read_feather() 분기가 있으나, 일반적으로 필요한 pyarrow는 의존성 파일에 명시되어 있지 않습니다.webui/run.py, webui/start.sh는 의존성을 확인·설치하는 분기를 갖습니다. 모델 가중치는 Python 의존성과 별도로 필요합니다.Dockerfile, package.json, Cargo.toml, go.mod, .github/workflows는 없습니다. 컨테이너·자동 배포·CI가 완성되어 있다고 가정하면 안 됩니다.PyTorch가 엔진, pandas가 재료 손질대라면 Flask와 HTML은 계기판입니다. Qlib는 별도의 주행 시험장입니다. 계기판이 있다는 사실만으로 엔진의 성능 측정이나 운영 배포까지 끝난 것은 아닙니다.
두 requirements 파일은 완전한 잠금 파일이 아닙니다. 기본 의존성 설치만으로 모든 예제·학습·테스트가 준비된다고 볼 수 없습니다. 특히 use_comet=False로 바꾸어도 Qlib 학습 스크립트의 최상위 import comet_ml은 남습니다. 실행 목적별 import와 필요 데이터를 먼저 구분해야 합니다.
아래 구조에서 B는 한 번에 처리하는 시계열 수, T는 과거 길이, H는 예측 길이입니다. S는 시계열 하나당 생성할 경로 수입니다. 모델의 실질적인 입력 채널은 여섯 개이고, 시간 정보는 별도의 다섯 채널로 전달됩니다.
[CSV / pandas DataFrame] [과거·미래 시각]
| |
v v
KronosPredictor.predict() calc_time_stamps()
검사·열 보완·정규화·clip 분/시/요일/일/월
| |
| x: (B,T,6) | (B,T,5), (B,H,5)
+----------------+----------------+
v
auto_regressive_inference()
경로 복제: B --> B*S
|
v
KronosTokenizer.encode()
Linear --> Transformer --> BSQ
|
s1, s2: (B*S,T)
|
+-------------v-----------------+
| 과거 토큰 + 시간 임베딩 |
| Kronos.decode_s1() --> s1 추출 |
| Kronos.decode_s2() --> s2 추출 |
| 새 토큰을 창에 추가 |
+-------------+-----------------+
| H회 반복
v
KronosTokenizer.decode()
마지막 창의 수치 복원
|
S개 경로의 평균
|
원래 단위로 역정규화
v
미래 DataFrame: (H,6) / 시계열
[학습] Qlib/CSV --> Dataset --> 토크나이저 복원 학습
--> 다음 토큰 예측 학습
[화면] HTML --> Flask JSON API --> Predictor --> 차트 JSON
[저장] Hub 가중치 / 학습 체크포인트 / 예측 결과 JSON
KronosTokenizer.forward()는 입력을 선형 변환하고 Transformer를 거쳐 s1_bits + s2_bits 차원으로 줄입니다. model/module.py::BSQuantizer.forward()는 이를 정규화한 뒤 이진 양자화를 수행합니다. encode(..., half=True)는 앞부분과 뒷부분의 비트를 각각 정수 토큰으로 돌려줍니다.
공개 Tokenizer-base 설정은 d_in=6, d_model=256, s1_bits=10, s2_bits=10, group_size=4입니다. 각 계층의 후보는 1,024개이며, 두 번호를 합친 이론적 조합은 2의 20승입니다. 백만 개의 학습 가능한 코드 벡터를 그대로 저장한다는 뜻은 아닙니다.
# model/module.py::BinarySphericalQuantizer.quantize() 핵심
zhat = torch.where(z > 0, 1, -1)
quantized = z + (zhat - z).detach()
# BSQuantizer는 먼저 F.normalize(z, dim=-1)를 적용한다.
# 순전파에서는 부호로 바꾸고, 역전파에서는 z로 기울기를 보낸다.
detach() 표현은 계산할 때는 이산값을 쓰되 학습 신호는 원래 값으로 통과시키는 방법입니다. 흔히 직선 통과 추정기라고 부릅니다.토크나이저 학습은 z_pre와 z라는 두 복원값을 만듭니다. 앞 계층만으로도 원래 입력을 복원하게 하고, 두 계층을 모두 쓰면 더 풍부하게 복원하게 합니다. finetune/train_tokenizer.py::train_model()의 손실은 (MSE(z_pre,x) + MSE(z,x) + bsq_loss) / 2입니다. 양자화 손실에는 표현이 양자화 결과에 가까워지도록 하는 항과 코드 사용 분포를 조절하는 항이 들어갑니다.
공개 설정의 n_enc_layers=4를 그대로 “Transformer 블록 네 개”라고 읽는 것도 주의해야 합니다. 생성자는 range(self.enc_layers - 1)로 블록을 만듭니다. 디코더도 같은 방식입니다. 설정 이름보다 실제 생성 코드를 읽는 습관을 배우기 좋은 대목입니다.
HierarchicalEmbedding.forward()는 s1·s2 임베딩을 이어 붙인 뒤 선형 투영합니다. 여기에 TemporalEmbedding이 분·시·요일·일·월 임베딩의 합을 더합니다. 그 결과를 TransformerBlock들이 읽습니다. 이 블록은 RMSNorm → Attention → 잔차 연결 → RMSNorm → 게이트형 FeedForward → 잔차 연결 순서입니다.
# model/kronos.py::auto_regressive_inference() 흐름을 축약
s1_logits, context = model.decode_s1(s1_ids, s2_ids, stamp)
s1_next = sample_from_logits(s1_logits[:, -1, :], ...)
s2_logits = model.decode_s2(context, s1_next)
s2_next = sample_from_logits(s2_logits[:, -1, :], ...)
# 개념적인 확률 분해 — 정확한 미래값을 확정하는 식이 아니다.
P(다음 토큰 쌍 (s1, s2) | 과거)
= P(s1 | 과거) * P(s2 | 과거, 이번에 뽑은 s1)
DualHead는 s1·s2의 출력층을 따로 두고, DependencyAwareLayer는 s1 임베딩을 질의로 사용해 문맥을 참조합니다. 핵심은 두 번호를 독립적으로 뽑지 않는다는 점입니다. 앞 계층을 먼저 정한 뒤 뒤 계층의 확률을 계산합니다.
Kronos.forward()에는 use_teacher_forcing 옵션도 있습니다. 그러나 finetune/train_predictor.py의 실제 호출은 이 옵션을 넘기지 않습니다. 기본 분기에서는 모델이 뽑은 s1으로 s2를 조건화합니다. 정답 s1을 항상 넣어 학습한다고 단정하면 구현과 달라집니다.
MultiHeadAttentionWithRoPE.forward()는 PyTorch의 scaled_dot_product_attention(..., is_causal=True)를 사용합니다. 시간 순서 뒤쪽을 보지 못하도록 하는 장치입니다. 위치 관계를 표현하는 RoPE와 달력 정보를 표현하는 시간 임베딩은 서로 다른 역할입니다.
KronosPredictor.predict()는 사용자와 모델 사이의 창구입니다. DataFrame 여부와 필수 가격 열을 검사하고, 거래량·거래대금 열을 보완한 뒤 여섯 입력 열의 결측값을 검사합니다. 열별 평균을 빼고 표준편차에 1e-5를 더한 값으로 나눕니다. 기본 clip=5 범위로 자르고, 결과는 같은 통계로 되돌립니다.
| 입력 상태 | predict()·predict_batch()의 실제 동작 |
|---|---|
| 가격 열 누락 | 시가·고가·저가·종가 중 하나라도 없으면 오류 |
volume 없음 | volume과 amount를 모두 0으로 설정. 기존 amount가 있어도 덮어씀 |
| 거래량만 있음 | amount = volume × OHLC 평균으로 근사 |
| 두 거래 열 모두 있음 | 해당 수치를 입력에 사용 |
| 열 보완 후 입력 열에 NaN | 여섯 입력 열 중 NaN이 남으면 오류. volume이 없어서 덮어쓴 기존 amount의 NaN은 검사 대상에서 사라짐. 무한대·시간 정렬·경제적 타당성까지 모두 검사하는 것은 아님 |
README의 배치 설명에는 선택 열이 없으면 0으로 채운다는 안내가 있지만, 코드는 위처럼 경우를 나눕니다. 또 calc_time_stamps()는 .dt 접근자를 쓰므로, 문서화된 일부 타입 표현만 믿기보다 datetime형 pandas Series를 전달하는 편이 정확합니다.
auto_regressive_inference()는 처음 입력을 sample_count만큼 복제합니다. 매 단계 s1·s2를 생성하고 토큰 버퍼에 추가하며, 버퍼가 차면 torch.roll(..., shifts=-1)로 오래된 항목을 밀어냅니다. 마지막에는 수치로 복원한 경로들을 np.mean(..., axis=1)로 평균합니다.
sample_count를 늘려도 현재 공개 API가 반환하는 것은 여러 경로의 평균이며, 경로별 분포나 신뢰구간은 반환하지 않습니다. 여러 경로를 평균했다는 사실과 불확실성을 정량화했다는 사실은 구분해야 합니다.
max_context로 제한하지만, 원래 입력의 정규화와 토크나이저 인코딩은 그 전에 수행됩니다. 긴 입력의 계산·메모리 비용까지 자동으로 사라지는 것은 아닙니다.max_context 구간만 처리합니다. pred_len이 이보다 크면 요청 길이 전체가 복원되지 않아 DataFrame 생성에 문제가 생길 수 있습니다. 연습에서는 예측 길이를 문맥 한도 이내로 둡니다.top_k_top_p_filtering()는 top_k > 0인 분기에서 바로 반환합니다. 두 값을 함께 설정해도 이 구현은 top-k와 top-p를 연속 적용하지 않습니다.긴 두루마리 위에 작은 창을 놓고 한 칸씩 미는 방식입니다. 다음 칸을 만들 때 보는 범위는 제한되지만, 창 밖의 과거를 사전에 읽고 정리하는 비용은 남을 수 있습니다. “입력을 자른다”는 설명을 볼 때는 어느 단계에서 자르는지까지 확인하세요.
Kronos/ |-- model/ 핵심 신경망과 예측 API | |-- kronos.py Tokenizer / Kronos / Predictor | |-- module.py BSQ / Attention / 임베딩 / 손실 | `-- __init__.py 공개 객체와 get_model_class() |-- finetune/ Qlib 기반 미세조정·백테스트 | |-- config.py 경로·기간·하이퍼파라미터 | |-- dataset.py QlibDataset와 과거 기반 정규화 | |-- qlib_data_preprocess.py | |-- train_tokenizer.py | |-- train_predictor.py | |-- qlib_test.py 예측 점수와 포트폴리오 평가 | `-- utils/ DDP·시드·모델 크기 도우미 |-- finetune_csv/ CSV 기반 별도 미세조정 경로 | |-- configs/ YAML 설정 예시 | |-- data/ 홍콩 종목 5분봉 CSV 예시 | |-- config_loader.py YAML 읽기·출력 경로 계산 | |-- finetune_base_model.py | |-- finetune_tokenizer.py | `-- train_sequential.py 두 단계 순차 실행 |-- examples/ 추론·데이터 수집·GUI·백테스트 예제 |-- tests/ CPU 회귀 테스트 | `-- data/ 입력 CSV·기대 출력·생성 도구 |-- webui/ Flask 앱과 실행 도우미 | |-- templates/index.html | |-- prediction_results/ 예측 결과 JSON 예시 | `-- requirements.txt |-- figures/ README용 그림 |-- requirements.txt |-- README.md `-- LICENSE
| 읽는 목적 | 권장 경로 | 구분할 점 |
|---|---|---|
| 첫 예측 흐름 | examples/prediction_example.py → predict() | 예제가 참조하는 루트 data/XSHG_5min_600977.csv는 커밋에 포함되지 않음 |
| 모델 원리 | model/kronos.py → model/module.py | 핵심 Python 3파일, 총 1,249줄 |
| 학습 원리 | finetune/dataset.py → 두 finetune/train_*.py | Qlib 데이터와 CUDA 분산 환경을 전제하는 경로 |
| CSV로 확장 | finetune_csv/config_loader.py → CustomKlineDataset | Qlib 경로와 정규화·장치 선택·설정 규칙이 같지 않음 |
| 결과를 화면에 | webui/app.py → webui/templates/index.html | 모델 함수와 HTTP 계층 사이에서 데이터가 다시 가공됨 |
저장소 전체는 파일 92개이며 Python 30파일·11,196줄입니다. 그중 examples/가 6,060줄로 약 54%를 차지합니다. 따라서 전체 줄 수만 보고 핵심 모델이 거대하다고 판단할 필요는 없습니다. 우선 API 한 함수 → 그 함수가 호출하는 모델 → 학습 데이터 순서로 읽으면 범위를 줄일 수 있습니다.
예제 CSV가 있는 곳과 기본 예제가 찾는 경로는 서로 다릅니다. tests/data/regression_input.csv는 입력 계약을 공부할 때 유용하고, finetune_csv/data/HK_ali_09988_kline_5min_all.csv는 CSV 학습 형식의 실제 예시입니다. 저장소에 일부 시세가 있다고 해서 모든 예제가 그대로 준비되는 것은 아닙니다.
초보자가 가장 자주 놓치는 것은 텐서의 축입니다. Qlib 기본 설정은 lookback_window=90, predict_window=10이고, QlibDataset.window는 여기에 1을 더한 101입니다. 다음 토큰을 정답으로 만들 때 한 칸이 필요하기 때문입니다.
# finetune/train_predictor.py::train_model()의 학습 준비
# batch_x: (B, 101, 6), 각 token_seq: (B, 101)
token_seq_0, token_seq_1 = tokenizer.encode(batch_x, half=True)
token_in = [token_seq_0[:, :-1], token_seq_1[:, :-1]]
token_out = [token_seq_0[:, 1:], token_seq_1[:, 1:]]
# token_in: t=0..99, token_out: t=1..100
# s1_logits: (B, 100, 1024) — 공개 small 설정 기준
커밋 67b630e의 학습 루프는 batch_x를 장치에 옮길 때 배치 축을 유지합니다. 크기가 1인 축을 무조건 없애는 squeeze()를 넣으면, 마지막 배치만 1개일 때 배열 모양이 달라질 수 있습니다. 반면 단일 예측 API에서 쓰는 squeeze(0)는 반환을 위해 의도적으로 배치 축을 제거하는 자리입니다.
실제 가중치 없이 종이에 (1,101,6), (2,101,6)를 적고 squeeze()와 squeeze(0)의 결과를 각각 써 보세요. 이어서 reshape(-1, vocab_size)가 교차 엔트로피 계산을 위해 어떤 축을 합치는지 설명해 보세요.
finetune/dataset.py::QlibDataset.__getitem__()는 처음 90개 관측으로 평균·표준편차를 계산하고, 그 통계를 전체 101개에 적용합니다. 반면 finetune_csv/finetune_base_model.py::CustomKlineDataset.__getitem__()는 전체 창으로 평균·표준편차를 계산합니다. 후자는 미래 평가 구간의 값이 과거 입력 변환에 영향을 줄 수 있는 구조입니다.
# QlibDataset의 원리
past_x = x[:lookback_window]
mean = past_x.mean(axis=0)
std = past_x.std(axis=0)
normalized = (x - mean) / (std + 1e-5)
# CSV 경로의 현재 원리 — x에는 뒤쪽 구간도 포함된다.
mean_all = x.mean(axis=0)
std_all = x.std(axis=0)
모델이 미래를 보지 않도록 Attention을 가려도, 전처리 통계에 미래값이 섞이면 평가의 공정성은 무너질 수 있습니다. 위 두 경로를 비교하면 데이터 누수가 모델 바깥에서도 생긴다는 사실을 배울 수 있습니다.
Qlib 경로도 별도 점검이 필요합니다. finetune/config.py의 학습 기간은 2022-12-31까지인데 검증 기간은 2022-09-01부터 시작합니다. 주석은 과거 문맥 확보를 위한 중첩이라고 설명하지만, prepare_dataset()는 날짜 마스크로 자르고 QlibDataset는 그 안에서 창을 고릅니다. 중첩 구간이 문맥으로만 쓰이도록 정답 위치를 제한하는 장치는 이 두 함수에 보이지 않습니다.
과거 4개는 고정하고 미래 2개만 10배로 바꾼 장난감 배열을 만드세요. 과거 기준 정규화와 전체 기준 정규화에서 처음 4개의 변환값이 달라지는지 비교하면 됩니다. 이어서 학습·검증 창의 정답 날짜 집합이 겹치는지도 점검해 보세요.
| 구현 | 배울 질문 | 작은 실습 |
|---|---|---|
RMSNorm | 값의 크기를 조절하는 정규화가 왜 블록 앞에 있을까? | 수치 3개로 제곱 평균의 제곱근을 직접 계산 |
RotaryPositionalEmbedding | 순서 정보와 날짜 정보는 왜 따로 필요할까? | 같은 가격열의 시각만 한 시간 옮겼을 때 입력 차이를 설명 |
FeedForward | silu(w1(x)) * w3(x)의 곱은 무엇을 제어할까? | 두 갈래 변환과 최종 투영의 입출력 차원을 도식화 |
TemporalEmbedding | learn_te가 고정·학습 임베딩 중 무엇을 선택할까? | 분·시·요일·일·월 임베딩의 항목 수 60·24·7·32·13을 확인 |
공개 Kronos-small 설정은 d_model=512, n_heads=8, n_layers=8, ff_dim=1024, learn_te=true입니다. 한 Attention 헤드의 폭은 512÷8=64입니다. 생성자의 일반 매개변수와 실제 공개 체크포인트의 설정을 함께 읽어야 구체적인 모양이 보입니다.
finetune/train_predictor.py::train_model()는 토크나이저를 torch.no_grad() 안에서 실행하고, 예측기만 AdamW로 갱신합니다. DualHead.compute_loss()는 s1·s2 교차 엔트로피의 평균을 사용합니다. OneCycleLR가 학습률을 바꾸고, 기울기는 최대 노름 3.0으로 제한합니다.
setup_ddp()와 DistributedSampler, dist.all_reduce()를 따라가면 장치별 계산과 전체 검증 손실 집계를 구분할 수 있습니다. 가장 좋은 검증 손실을 만났을 때 rank 0 프로세스가 save_pretrained()로 체크포인트를 저장합니다.
토크나이저 학습의 accumulation_steps는 여러 DataLoader 배치를 모으는 대신 한 배치를 잘라 여러 번 역전파하는 방식입니다. 따라서 출력 메시지의 “배치 크기 × 누적 횟수”를 실제 표본 수로 그대로 해석하면 안 됩니다. 배치 크기가 누적 횟수로 나누어떨어지지 않으면 정수 나눗셈 때문에 남는 표본도 확인해야 합니다.
배치 50개, 누적 횟수 2와 3을 가정해 start_idx·end_idx를 적으세요. 역전파마다 몇 표본을 보는지와, 옵티마이저를 한 번 갱신하기 전 총 몇 표본을 보는지가 어떻게 다른지 계산해 보세요.
tests/test_kronos_regression.py는 CPU·시드 123·문맥 길이 256/512·top_k=1 조건을 사용합니다. 모델과 토크나이저는 각각 MODEL_REVISION, TOKENIZER_REVISION으로 Hub 리비전을 고정합니다. 첫 테스트는 저장된 CSV 출력과 상대 허용오차 1e-5로 비교하고, 다른 테스트는 정해진 표본의 평균 MSE가 기대값과 가까운지 확인합니다.
이런 테스트는 코드 변경 후 동작이 달라졌는지 찾는 데 적합합니다. 반면 새로운 시장에서 기준선보다 잘 예측하는지, 거래 비용을 감안해도 의미가 있는지는 별도의 평가입니다. 고정 출력 재현 → 보지 않은 기간의 예측 → 전략 평가는 서로 다른 검증 단계입니다.
webui/app.py::predict()는 핵심 Predictor와 같은 이름이지만 책임이 다릅니다. HTTP 본문을 읽고 파일을 열며, 날짜 구간과 표시용 데이터를 만든 뒤 모델에 넘깁니다. 다음 차이를 실제 코드와 대조하면 서비스 계층의 역할이 선명해집니다.
| 표면적인 안내 | 코드에서 확인할 사실 |
|---|---|
| 최신 데이터 예측 | 시작일을 주지 않은 분기는 df.iloc[:lookback]를 사용함. 마지막 구간이 아니라 파일 앞부분임 |
| 거래대금 열 지원 | 로더는 amount를 읽지만 예측용 required_cols에서는 제외함. Predictor에서 다시 근사될 수 있음 |
| 모델이 없으면 모의 예측 | 안내 문구에는 남아 있지만 예측 라우트는 모델이 준비되지 않으면 HTTP 400을 반환함 |
| 데이터 자동 탐색 | load_data_files()는 루트 data/를 탐색함. CSV 학습 예제 폴더를 자동으로 탐색하지 않음 |
실습으로는 “입력 CSV → 모델에 전달한 열·시각 → 차트에 표시한 시각”을 한 줄씩 대응시켜 보세요. 화면이 그럴듯한 것과 데이터의 의미가 일관된 것은 다른 문제입니다.
README는 Python 3.10 이상을 안내하지만, GPU별 최소 VRAM이나 처리 속도 표는 제공하지 않습니다. 따라서 아래에서 코드가 선택할 수 있는 장치, 가중치만의 산술적 크기, 실험 설계 제안을 구분합니다. 확인되지 않은 “몇 GB면 반드시 된다”는 최소 사양으로 읽으면 안 됩니다.
| 모델 | 파라미터·문맥 길이 | FP32 가중치만 계산 | 공개 상태 |
|---|---|---|---|
| Kronos-mini | 4.1M · 2,048 | 약 16.4 MB | README상 공개, Tokenizer-2k와 짝 |
| Kronos-small | 24.7M · 512 | 약 98.8 MB | README상 공개, Tokenizer-base와 짝 |
| Kronos-base | 102.3M · 512 | 약 409.2 MB | README상 공개, Tokenizer-base와 짝 |
| Kronos-large | 499.2M · 512 | 약 1,996.8 MB | README상 미공개 |
모델 크기·문맥 길이·공개 여부의 출처는 README의 Model Zoo입니다. 저장량은 표의 반올림된 파라미터 수에 4바이트를 곱한 십진 MB 계산입니다. 토크나이저 가중치, 중간 텐서, Attention, 라이브러리, 학습 기울기·옵티마이저 상태는 포함하지 않았습니다. 다운로드 파일의 실제 크기나 최소 RAM·VRAM 측정값이 아닙니다.
| 목적 | 필요한 환경·근거 | 처음 줄일 값 |
|---|---|---|
| 소스·CSV 읽기 | 일반 텍스트 편집기와 데이터 도구. GPU·가중치 없이 학습 가능 | 읽을 파일 범위를 핵심 모델과 Dataset으로 제한 |
| 작은 추론 | KronosPredictor.__init__()는 CUDA → MPS → CPU 순으로 자동 선택 | lookback=256, pred_len=8, sample_count=1부터 실험 |
| Web UI | Flask 프로세스에 모델도 함께 로드. HTTP 요청과 추론 자원이 같은 프로세스에 있음 | 한 사용자·한 요청 기준으로 먼저 측정 |
| Qlib 미세조정 | setup_ddp()의 NCCL과 cuda:local_rank 선택 때문에 CUDA 학습 환경을 전제 | batch_size, 창 길이, 데이터 로더 작업자 수 |
| CSV 미세조정 | SequentialTrainer._setup_device()에 CUDA·CPU 분기가 있음. MPS 학습 선택 분기는 없음 | 설정 경로와 데이터 분할을 검토한 뒤 작은 배치로 설계 |
CPU·MPS 분기가 있다는 사실은 모든 운영체제·PyTorch 버전에서 실행이 검증되었다는 의미는 아닙니다. 첫 추론은 CPU의 작은 입력으로 기능을 확인하고, GPU가 필요해지면 같은 입력·샘플링 조건으로 시간과 메모리를 비교하는 순서를 권합니다.
파라미터 수가 작은 모델이라도 문맥 길이와 배치·샘플 수가 커지면 추론 메모리와 시간이 크게 늘어날 수 있습니다. 이 구현은 여러 경로를 실제 배치로 복제하므로, 한 시계열의 sample_count=5는 한 경로와 같은 양의 작업이 아닙니다.
B*S입니다. predict_batch()도 이를 고려해야 합니다.다음은 기본 추론을 위한 준비 명령 예시입니다. Web UI·Qlib·CSV 학습·테스트의 추가 의존성은 3절의 구분대로 따로 확인합니다. 저장소 코드와 모델 가중치의 버전도 각각 기록하세요.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
# Web UI를 공부할 때는 webui/requirements.txt도 확인한다.
# from_pretrained()의 첫 호출은 Hub 가중치를 내려받을 수 있다.
webui/app.py와 webui/run.py의 실행 설정은 debug=True, host='0.0.0.0', port=7070입니다. 인증·요청 큐·모델별 자원 격리까지 갖춘 운영 서버 구성은 아닙니다. 외부 공개 전에 디버그 모드·바인딩·파일 경로 입력·동시 추론 정책을 별도로 설계해야 합니다.
다음 예시는 저장소 루트에서 읽는 경로를 사용합니다. 처음에는 원본 파일을 보존하고 별도 실험 파일에서 진행하세요. 목표는 높은 수익률이 아니라, 입력 계약·배열 모양·검증 조건을 설명할 수 있는 결과를 만드는 것입니다.
tests/data/regression_input.csv의 열과 시간 순서를 검사하세요. 가중치를 읽지 않아도 할 수 있는 과제입니다. 단위·시간 간격·결측값·중복 시각을 적은 짧은 데이터 설명서를 만듭니다.
import pandas as pd
df = pd.read_csv(
"tests/data/regression_input.csv", parse_dates=["timestamps"]
)
cols = ["open", "high", "low", "close", "volume", "amount"]
print(df[cols].dtypes)
print("결측값:", df[cols].isna().sum().to_dict())
print("시간순:", df["timestamps"].is_monotonic_increasing)
print("중복 시각:", df["timestamps"].duplicated().sum())
valid_high = df["high"].ge(df[["open", "close", "low"]].max(axis=1))
valid_low = df["low"].le(df[["open", "close", "high"]].min(axis=1))
print("가격 관계 위반:", int((~(valid_high & valid_low)).sum()))
완료 기준: 정상 입력과 오류 입력을 각각 한 가지 이상 설명하고, “핵심 Predictor가 검사하는 조건”과 “직접 추가한 조건”을 구분합니다. 위 코드는 숫자형·결측값 결과를 먼저 확인한 뒤 가격 관계를 해석해야 합니다.
small 모델과 Tokenizer-base를 짝지어 문맥 256개, 예측 8개를 사용합니다. 아래 리비전은 회귀 테스트에 고정된 값입니다. 미래 구간에서는 시각만 모델에 전달하며 실제 가격은 평가용으로 남깁니다.
import pandas as pd
import torch
from model import Kronos, KronosTokenizer, KronosPredictor
torch.manual_seed(123)
df = pd.read_csv(
"tests/data/regression_input.csv", parse_dates=["timestamps"]
)
tokenizer = KronosTokenizer.from_pretrained(
"NeoQuasar/Kronos-Tokenizer-base",
revision="0e0117387f39004a9016484a186a908917e22426",
)
model = Kronos.from_pretrained(
"NeoQuasar/Kronos-small",
revision="901c26c1332695a2a8f243eb2f37243a37bea320",
)
tokenizer.eval()
model.eval()
predictor = KronosPredictor(model, tokenizer, device="cpu", max_context=512)
cols = ["open", "high", "low", "close", "volume", "amount"]
past_ts = df["timestamps"].iloc[:256].reset_index(drop=True)
future_ts = df["timestamps"].iloc[256:264].reset_index(drop=True)
pred = predictor.predict(
df=df[cols].iloc[:256], x_timestamp=past_ts, y_timestamp=future_ts,
pred_len=8, T=1.0, top_k=1, top_p=1.0,
sample_count=1, verbose=False,
)
assert pred.shape == (8, 6)
print(pred.head())
완료 기준: 출력 열 여섯 개와 반환 인덱스의 의미를 한국어로 설명합니다. 같은 환경·같은 입력의 반복 결과를 비교하고, no_grad()는 기울기 기록을 끄며 eval()은 Dropout 같은 층의 동작을 바꾼다는 차이도 적습니다. Predictor 자체는 eval()을 호출하지 않습니다.
학습된 모델 없이 6절의 정규화를 작은 배열로 옮겨 보세요. 과거는 고정하고 미래만 바꾸는 경우, 거래량이 없는 경우, 거래량만 있는 경우, NaN이 있는 경우를 각각 만듭니다. 기존 코드를 복제해 검증하기보다, 지켜야 할 데이터 성질을 먼저 정합니다.
amount가 “관측값·근사값·0” 중 무엇인지 사례별로 표시합니다.완료 기준: 두 Dataset 경로의 차이를 숫자 표로 보여 주고, CSV 학습 경로에서 바꿔야 할 통계 계산 범위를 제안합니다. 좋은 모델을 고르는 일과 좋은 입력 변환을 만드는 일이 별개임을 설명할 수 있어야 합니다.
여러 기준 시점에서 과거 256개로 다음 8개를 예측하는 평가표를 설계하세요. 모델 설정은 먼저 고정하고 검증 기간에서만 조정합니다. 마지막 종가를 반복하는 기준선을 같은 구간에 적용하고, 테스트 기간은 끝까지 남겨 둡니다.
완료 기준: 모델이 이긴 구간과 진 구간이 모두 드러나는 표를 만듭니다. 수익률을 추가한다면 수수료·체결 시점·슬리피지를 별도 명시합니다. finetune/qlib_test.py도 거래 비용을 넣지만, 그것만으로 모든 시장의 체결 현실을 표현하지는 않습니다.
별도 실험 모듈에서 auto_regressive_inference()의 경로 평균 직전 배열을 보존하는 인터페이스를 설계하세요. 반환 모양을 (B,S,H,6)으로 정의하고, 평균 API와 경로 API의 차이를 설명합니다.
sample_count와 문맥 길이를 하나씩 바꾸어 시간·메모리를 기록합니다. GPU에서는 측정 전후 동기화도 고려합니다.완료 기준: 경로별 출력과 그 평균이 일치함을 확인하고, 더 많은 경로에 드는 비용을 보고합니다. 모델 생성의 다양성과 통계적 보정의 차이를 설명하면 과제의 목적을 달성한 것입니다.
좋은 실습 결과는 예측선 한 장이 아니라, 어떤 입력·버전·시점·기준선으로 검증했는지 다시 따라갈 수 있는 기록입니다.
Python의 함수·클래스와 표 형태의 데이터를 읽을 수 있다는 전제의 학습 순서입니다. 매주 “읽을 소스”와 “만들 결과물”을 함께 정하면, 어려운 논문을 읽다가 길을 잃는 일을 줄일 수 있습니다. 시간이 부족하면 1·2·5주차를 먼저 진행해도 됩니다.
| 주차 | 공부할 기술 | 연결할 소스 | 주말에 남길 결과물 |
|---|---|---|---|
| 1주차 | pandas·시간 인덱스·OHLCVA·결측 처리 | predict(), calc_time_stamps(), 예제 CSV |
입력 열·단위·시각·검사 규칙을 정리한 데이터 설명서 |
| 2주차 | PyTorch 텐서·배치 축·자동미분·학습과 평가 모드 | QlibDataset, DualHead.compute_loss() |
배치 1·2·50일 때 입력과 정답의 모양 표 |
| 3주차 | 오토인코더·양자화·비트 인덱스·복원 손실 | KronosTokenizer, BSQuantizer |
장난감 비트열 ↔ 정수 변환과 두 계층 복원 구조도 |
| 4주차 | Attention·인과 마스크·RoPE·자기회귀 샘플링 | TransformerBlock, decode_s1(), decode_s2() |
다음 캔들 한 개를 만드는 호출 순서와 확률 분해 설명 |
| 5주차 | 시간 분할·정규화 누수·기준선·재현 가능한 평가 | 두 Dataset 구현, tests/test_kronos_regression.py |
고정 출력 회귀와 보지 않은 구간의 예측 평가를 구분한 보고서 |
| 6주차 | DDP·체크포인트·API 계약·자원 측정 | finetune/utils/training_utils.py, finetune/train_predictor.py, webui/app.py |
학습 데이터부터 화면 표시까지의 경계와 운영 확장 제안 |
처음부터 모든 수식을 이해하려 하기보다, 입력과 출력의 모양을 고정하고 그 사이의 한 함수를 설명하는 방식으로 깊이를 늘리세요. 토크나이저를 다 읽지 못했더라도, 미래값이 정규화에 섞이는 문제나 시간 인덱스가 어긋나는 문제는 먼저 이해할 수 있습니다.
먼저 Kronos의 전체 구조 그림과 입력 정의를 보고, 코드에서 두 토큰의 흐름을 찾으세요. 그다음 BSQ 논문의 양자화 원리로 돌아가고, 마지막에 평가 지표와 실험 조건을 읽습니다. 이 순서는 용어·코드·실험이 서로 무엇을 설명하는지 연결하는 데 도움이 됩니다.
| 용어 | 쉬운 설명 |
|---|---|
| 시계열 | 관측 순서 자체가 의미를 갖는 데이터. 금융에서는 같은 숫자라도 언제 관측했는지가 중요합니다. |
| 캔들·OHLCVA | 한 시간 구간의 시가·고가·저가·종가·거래량·거래대금을 묶은 표현입니다. |
| 파운데이션 모델 | 큰 데이터에서 미리 학습한 표현을 여러 대상과 작업에 재사용하려는 모델입니다. |
| 사전학습·미세조정 | 먼저 넓은 데이터로 배우고, 나중에 특정 데이터에 맞추어 추가 학습하는 두 단계입니다. |
| 토크나이저 | 여기서는 여섯 연속 수치를 정수 번호로 압축하고 다시 복원하는 학습된 변환기입니다. |
| 양자화·BSQ | 연속 표현을 제한된 코드로 바꾸는 것. BSQ는 정규화된 표현의 각 성분을 부호 기반 값으로 바꿉니다. |
| s1·s2 | 한 캔들을 표현하는 두 계층의 토큰. s1만으로도 복원을 배우며, s2는 s1에 조건화해 생성합니다. |
| 직선 통과 추정기 | 이산 선택을 순전파에 쓰면서 역전파에는 연속적인 학습 신호가 흐르도록 하는 근사 방법입니다. |
| 임베딩 | 토큰 번호나 요일 같은 범주를 신경망이 다룰 수 있는 벡터로 바꾸는 표현입니다. |
| Attention | 현재 위치를 계산할 때 다른 위치의 정보를 어떤 비중으로 참고할지 정하는 연산입니다. |
| 인과 마스크 | 시간 뒤쪽 위치의 정보를 앞쪽 위치가 미리 참고하지 못하게 가리는 규칙입니다. |
| RoPE | 질의와 키 벡터를 위치에 따라 회전시켜 순서 관계를 반영하는 위치 표현 방식입니다. |
| 자기회귀 | 이미 있는 값과 방금 만든 값을 사용해 다음 값을 하나씩 생성하는 방식입니다. |
| 문맥 길이 | 한 번의 모델 계산에서 참고하는 과거 토큰 수의 범위. 전체 데이터 길이와 같지 않을 수 있습니다. |
| 로짓·온도 T | 로짓은 확률로 바꾸기 전의 점수입니다. 온도는 이 점수를 나누어 분포의 집중 정도를 조절합니다. |
| top-k·top-p | 후보를 상위 k개 또는 누적 확률 기준으로 제한하는 샘플링 방식입니다. 이 구현에서는 top-k 분기가 우선합니다. |
| sample_count | 입력 하나에서 생성할 경로 수입니다. 현재 Predictor는 경로별 값 대신 그 평균을 반환합니다. |
| MSE·교차 엔트로피 | MSE는 연속값의 제곱 오차, 교차 엔트로피는 정답 토큰에 얼마나 확률을 주었는지 보는 분류 손실입니다. |
| 데이터 누수 | 실제 예측 시점에 알 수 없는 정보가 입력·전처리·모델 선택에 섞이는 문제입니다. |
| 워크포워드 평가 | 기준 시점을 앞으로 옮기며, 각 시점 이전의 정보로만 이후 구간을 평가하는 방법입니다. |
| DDP·rank | 여러 프로세스가 데이터를 나누어 계산하고 기울기를 동기화하는 학습 방식과, 각 프로세스의 번호입니다. |
| 체크포인트·리비전 | 체크포인트는 저장한 모델 상태, 리비전은 어떤 버전의 파일인지 지정하는 식별자입니다. |
| 회귀 테스트 | 코드 변경으로 이전 동작이 깨졌는지 확인하는 소프트웨어 검사입니다. 회귀 분석이라는 모델 종류와 구분합니다. |
| 백테스트·슬리피지 | 과거 데이터로 거래 규칙을 평가하는 절차와, 기대 가격과 실제 체결 가격 사이의 차이입니다. |
| 항목 | 상태·근거 |
|---|---|
| 공식 저장소 | shiyu-coder/Kronos |
| 선정 | GitHub Trending Python Weekly #13 · 2026-10-23 분석 |
| 스타·포크 | ⭐43,318 · 포크 7,137 — 2026-10-23 GitHub 기준 |
| 주 언어 | Python |
| 생성일 | 2025-07-01 |
| 최신 릴리스 | GitHub 릴리스 없음. Hugging Face의 모델 가중치 공개와는 별개 |
| 최근 push | 2026-04-13 |
| 최근 커밋 | 67b630e · 2026-04-13 · Merge pull request #243 from ElhamDevelopmentStudio/fix/batch-dimension-training |
| Python 규모 | 30파일 · 11,196줄. 핵심 model/은 3파일 · 1,249줄 |
| 화면·설정 규모 | HTML 1,238줄, Shell 40줄, YAML 72줄. HTML 안의 JavaScript 약 600줄·CSS 약 430줄은 HTML과 중복 집계 |
| 규모 산정 범위 | 저장소 파일 92개. 물리적 줄 수이며 빈 줄·주석 포함. CSV·JSON 데이터와 이미지·문서는 코드 줄 수에서 제외 |
| 라이선스 | MIT · 저작권 표기 Copyright (c) 2025 ShiYu |
KronosTokenizer, Kronos, KronosPredictor, 생성 루프학습 규모와 성능 개선의 출처는 논문·README이며, 전체 사전학습 데이터와 평가 자료가 저장소에 포함된 것은 아닙니다. 구현 설명은 커밋 67b630e 기준입니다.
두 학습 경로는 정규화와 데이터 분할 규칙이 다릅니다. CSV 경로의 미래 통계 사용과 Qlib 분할의 날짜 중첩을 먼저 검토해야 합니다. README도 Qlib 파이프라인을 단순화된 미세조정 예제로 설명하며, 완성된 실거래 시스템으로 제시하지 않습니다.
예측 출력에는 고가·저가의 순서, 거래량의 비음수성 같은 시장 제약을 강제로 만족시키는 후처리가 보이지 않습니다. y_timestamp는 미래 가격이 아니라 미래 시각이며, 휴장·거래 세션·시간대의 적절성은 호출자가 관리해야 합니다. 여러 경로의 평균도 실제로 관측 가능한 캔들 경로라고 보장되지 않습니다.
회귀 테스트의 일치나 보기 좋은 예측선은 새로운 시장에서의 성능·수익을 보장하지 않습니다. 가중치 리비전과 입력 단위를 기록하고, 보지 않은 기간의 기준선 비교 및 비용·체결 가정을 갖춘 평가를 별도로 진행하세요.