<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jeonghwan723.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jeonghwan723.github.io/" rel="alternate" type="text/html" /><updated>2026-11-02T10:29:41+00:00</updated><id>https://jeonghwan723.github.io/feed.xml</id><title type="html">Jeong-Hwan Kim</title><subtitle>Climate Scientist</subtitle><author><name>Jeong-Hwan Kim</name><email>spikio19@naver.com</email></author><entry><title type="html">[논문 리뷰] Skillful joint probabilistic weather forecasting from marginals (Alet et al., 2025)</title><link href="https://jeonghwan723.github.io/skillful-joint-probabilistic-weather-forecasting-from-marginals-alet-et-al-2025/" rel="alternate" type="text/html" title="[논문 리뷰] Skillful joint probabilistic weather forecasting from marginals (Alet et al., 2025)" /><published>2026-06-30T00:00:00+00:00</published><updated>2026-06-30T00:00:00+00:00</updated><id>https://jeonghwan723.github.io/skillful-joint-probabilistic-weather-forecasting-from-marginals-alet-et-al-2025</id><content type="html" xml:base="https://jeonghwan723.github.io/skillful-joint-probabilistic-weather-forecasting-from-marginals-alet-et-al-2025/"><![CDATA[<h2 id="요약">요약</h2>

<ul>
  <li>FGN(Functional Generative Network)은 DeepMind의 확률 앙상블 기상 모델로, 2025년 11월 공개된 WeatherNext 2의 엔진이다.</li>
  <li>핵심 아이디어: noise를 입·출력이 아니라 신경망 파라미터 공간에 conditional layer normalization을 통해, 저차원(32-dim) 전역 노이즈 하나로 주입한다.</li>
  <li>학습은 각 격자점의 marginal CRPS만 최소화한다. 그런데 추론 시에는 공간적으로 일관된 joint 분포가 나온다. 이게 제목 그대로 “from marginals”의 의미이다.</li>
  <li>Diffusion 기반 GenCast가 매 step마다 여러 차례의 denoising forward pass를 도는 반면, FGN은 step당 single forward pass다. 그래서 모델이 더 큰데도(180M vs 57M) 8배 빠르다.</li>
  <li>결과: GenCast와 ENS를 deterministic·probabilistic 지표 전반에서 추월, 열대 저기압 경로 예측에서 유의하게 우수(p&lt;0.05).</li>
</ul>

<hr />

<h2 id="1-배경-왜-새-확률-모델인가">1. 배경: 왜 새 확률 모델인가</h2>

<p>기상 예측에서 확률 예보는 필수적이다. 대기 역학은 비선형이고 관측은 부분적이라, 아무리 좋은 모델도 단일 결정론적 예측으로는 이로 인한 불확실성을 고려하기 어렵다. 의사결정자는 가장 그럴듯한 시나리오뿐 아니라 위험한 꼬리 시나리오(극한 폭풍 등)까지 알아야 한다.</p>

<p>ML 확률 예측의 분수령은 GenCast였다. ECMWF의 운영 앙상블 ENS를 고해상도에서 처음으로 분명하게 추월한 ML 모델이다. 다만 GenCast는 diffusion 모델이라, 한 timestep을 만들기 위해 반복적 denoising을 돌아야 해서 다른 ML 모델 대비 느리고, dense gridded data 바깥으로 확장하기에 유연하지 않았다.</p>

<p>FGN은 더 빠르고 더 정확한 확률 모델을 같은 GNN+graph-transformer 백본 위에서 전혀 다른 생성 방식으로 앙상블을 만들어낸다.</p>

<hr />

<h2 id="2-문제-설정">2. 문제 설정</h2>

<p>목표는 표준적이다. 직전 두 스텝을 조건으로 다음 스텝의 분포에서 샘플링하는 2차 마르코프 분해다.</p>

\[p(X^{1:T} \mid X^0, X^{-1}) = \prod_{t=1}^{T} p\big(X^t \mid X^{t-2:t-1}\big)\]

<p>각 상태 $X^t$ 는 6개 대기 변수 × 13개 기압면 + 6개 지표 변수를 0.25° 격자 위에 담는다. 6시간 timestep으로 15일 예측을 타깃한다. 앙상블은 이 autoregressive 샘플링을 여러 번 반복해 만든다.</p>

<p>여기까지는 GenCast와 동일하다. 차이는 “한 스텝의 분포에서 어떻게 샘플을 뽑느냐” 에 있다.</p>

<hr />

<h2 id="3-핵심-아이디어-불확실성을-파라미터-공간에-넣는다">3. 핵심 아이디어: 불확실성을 파라미터 공간에 넣는다</h2>

<p>FGN은 불확실성을 두 종류로 깔끔하게 분리한다.</p>

<h3 id="epistemic-uncertainty--deep-ensemble">Epistemic uncertainty — deep ensemble</h3>

<p>모델 자체에 대한 불확실성(유한한 데이터·불완전한 학습)이다. 이는 deep ensemble(혹은 random seed ensemble)로 처리한다. 독립적으로 초기화·학습한 모델 $J=4$ 개를 두고, 각 모델이 앙상블 멤버의 일부를 생성한다. 베이즈 사후예측분포</p>

\[p(X^{1:T}\mid X^{\leq 0}, \mathcal{D}) = \int p(X^{1:T}\mid X^{\leq 0}, \mathcal{M}),p(\mathcal{M}\mid\mathcal{D}),d\mathcal{M}\]

<p>를 4개 시드로 근사하는 셈이다.</p>

<h3 id="aleatoric-uncertainty--learned-functional-perturbation">Aleatoric uncertainty — learned functional perturbation</h3>

<p>진짜 새로운 부분은 여기다. 미해상(unresolved) 현상에서 오는 환원 불가능한 randomness를, FGN은 신경망 파라미터를 샘플링해서 표현한다.</p>

\[X_i^t = f_{\theta_i^t}(X^{t-2:t-1}), \qquad \theta_i^t \sim \mathcal{P}_\mathcal{M}(\theta)\]

<p>즉 앙상블 멤버 $i$ 마다, timestep $t$ 마다 <em>서로 다른 신경망</em>을 뽑아서 예측을 만든다. 이게 “Functional” Generative Network라는 이름의 출처다. 노이즈를 입력 필드나 출력 필드에 더하는 대신, 함수 그 자체를 확률변수로 본다.</p>

<p>왜 이게 더 나은가? Alet(2022)의 논거를 빌리면, 파라미터 공간의 변동은 입력·출력 공간의 변동보다 더 구조화된(structured) 변화를 만든다. 파라미터를 공유하는 레이어(예: 공간 차원에 걸쳐 같은 가중치를 쓰는 conv/GNN)에서는, 샘플링된 파라미터가 모든 공간 위치에 <em>일관되게</em> 재사용되기 때문이다. 이것이 뒤에서 다룰 “marginal → joint” 마법의 토대다. 수치 모델의 stochastic physics(파라미터를 흔들어 앙상블을 만드는 기법)와 직접적으로 닮아 있다.</p>

<h3 id="reparameterization-trick">Reparameterization trick</h3>

<p>파라미터 분포 $\mathcal{P}_\mathcal{M}(\theta)$ 는 잘 알려진 reparameterization trick으로 학습한다.</p>

\[\theta = \theta^* + \Delta \cdot \epsilon, \qquad \epsilon \sim \mathcal{N}(0, I)\]

<ul>
  <li>$\theta^*$ : 평균 파라미터 — 결정론적 함수에 해당</li>
  <li>$\Delta$ : 노이즈를 파라미터 섭동으로 매핑하는 (구조화될 수 있는) 행렬 — 공분산을 통제</li>
  <li>모델 $\mathcal{M} := {\theta^*, \Delta}$</li>
</ul>

<p>추론 시에는 두 불확실성을 조합한다. 먼저 4개 모델 중 하나 $\mathcal{M}_j$ 를 뽑고(epistemic), 그 안에서 $\epsilon_i^t$ 를 뽑아(aleatoric)</p>

\[\theta_i^t = \theta_j^* + \Delta_j \cdot \epsilon_i^t\]

<p>를 만든다.</p>

<hr />

<h2 id="4-어떻게-주입하나-conditional-layer-norm--32차원-전역-노이즈">4. 어떻게 주입하나: Conditional Layer Norm + 32차원 전역 노이즈</h2>

<p>이론은 “파라미터를 샘플링한다”지만, 180M 파라미터를 통째로 샘플링하진 않는다. FGN의 영리한 구현 선택은 conditional layer normalization을 perturbation 통로로 재활용하는 것이다.</p>

<p>GenCast에서 conditional norm은 diffusion noise level $\sigma$ 를 조건으로 주입하는 데 쓰였다. FGN은 같은 메커니즘을 다르게 쓴다.</p>

<blockquote>
  <p>전역 노이즈 벡터 $z \sim \mathcal{N}(0,1)^{32}$ 를 네트워크의 모든 conditional layer-norm 레이어에 흘려보낸다. 앙상블 멤버마다 다른 $z$ 를 뽑는 것이, 곧 앙상블의 분산을 만드는 유일한 원천이다.</p>
</blockquote>

<p>이 설계의 두 가지 특징이 핵심이다.</p>

<ol>
  <li>저차원 노이즈 — 단 32차원. 무한히 많은 자유도가 아니라, 빡빡한 병목.</li>
  <li>전역 적용 + 공간 공유 — 이 $z$ 가 모든 레이어에 동일하게 들어가고, conditional norm 파라미터는 공간 차원(mesh·grid 노드)에 걸쳐 공유된다.</li>
</ol>

<p>두 특징이 합쳐지면, 모델은 공간적으로 제멋대로인 노이즈를 만들 수 없게 된다. 변동을 주려면 전 지구에 걸쳐 일관된 방식으로 줄 수밖에 없다. 바로 이 제약이 다음 절의 마법을 가능케 한다.</p>

<blockquote>
  <p>참고로 AIFS-CRPS(Lang et al., 2024)도 conditional norm으로 확률성을 주입하지만, <em>위치별(location-specific)</em> 조건을 써서 고주파 공간 성분을 더한다. FGN은 의도적으로 반대 방향, 즉 전역·저주파·일관성을 택했다.</p>
</blockquote>

<hr />

<h2 id="5-이-논문의-핵심-왜-marginal로-학습했는데-joint가-나오는가">5. 이 논문의 핵심: 왜 marginal로 학습했는데 joint가 나오는가</h2>

<p>FGN은 fair CRPS(fCRPS) 를 손실로 쓴다.</p>

\[\text{fCRPS}(x^{1:N}, y) := \frac{1}{N}\sum_n |x^n - y| - \frac{1}{2N(N-1)}\sum_{n,n'}|x^n - x^{n'}|\]

<p>학습 시에는 $N=2$ 샘플만으로, 모든 위치·변수·기압면에 대해 평균한다. CRPS는 단변량(univariate) 분포에 대해서만 strictly proper scoring rule이다. 즉, FGN의 손실은 각 격자점의 <em>marginal</em> 분포만 제약한다.</p>

<p>여기서 자연스러운 의문이 생긴다.</p>

<blockquote>
  <p>marginal만 맞추면, 공간 상관구조(joint)는 엉망이어도 손실이 0이 될 수 있지 않나? 각 픽셀이 독립적으로 옳은 분포를 내놓으면서 서로 아무 상관이 없는, 물리적으로 말이 안 되는 앙상블도 marginal 점수는 만점이다.</p>
</blockquote>

<p>FGN의 답은 “우리 아키텍처는 그런 앙상블을 만들 수 없다” 이다. 유일한 randomness 원천이 32차원 전역 $z$ 이고, 그게 공간 공유된 conditional norm을 통해서만 들어가므로, 픽셀별 독립 노이즈는 <em>구조적으로 불가능</em>하다. 모델이 marginal CRPS를 낮추려고 변동을 만들면, 그 변동은 어쩔 수 없이 전 지구에 걸쳐 일관된 — 즉 그럴듯한 joint 구조를 가진 — 형태가 된다.</p>

<p>정리하면 이렇다.</p>

\[\underbrace{\text{marginal-only 손실 (최적화 쉬움)}}*{\text{fCRPS}} ;+; \underbrace{\text{저차원·전역 파라미터 노이즈 (강한 inductive bias)}}*{\text{conditional LN, } z\in\mathbb{R}^{32}} ;\Longrightarrow; \text{skillful joint}\]

<p>고차원에서 joint를 직접 학습하는 건 어렵고 비싸다. FGN은 그 어려운 목적함수를 쉬운 marginal 손실 + 영리한 inductive bias로 우회한다. joint 구조는 따로 학습한 게 아니라 제약에서 “공짜로” 떨어진다. 이게 논문 전체를 관통하는 한 방이다.</p>

<hr />

<h2 id="6-아키텍처와-학습">6. 아키텍처와 학습</h2>

<h3 id="백본">백본</h3>

<p>GenCast denoiser와 거의 동일한 구조다. lat/lon 격자를 GNN encoder로 6번 정련한 구면 정이십면체 mesh의 latent 공간으로 보내고, 그 노드 위에서 graph-transformer processor가 작동한 뒤, decoder로 되돌린다.</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>GenCast</th>
      <th>FGN</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>생성 방식</td>
      <td>diffusion (반복 denoising)</td>
      <td>single forward pass</td>
    </tr>
    <tr>
      <td>파라미터</td>
      <td>~57M (전체)</td>
      <td>~180M (시드당) × 4 시드</td>
    </tr>
    <tr>
      <td>latent dim</td>
      <td>512</td>
      <td>768</td>
    </tr>
    <tr>
      <td>processor layers</td>
      <td>16</td>
      <td>24</td>
    </tr>
    <tr>
      <td>timestep</td>
      <td>12h</td>
      <td>6h</td>
    </tr>
    <tr>
      <td>noise 주입</td>
      <td>diffusion $\sigma$</td>
      <td>32-dim $z$ → conditional LN</td>
    </tr>
    <tr>
      <td>학습 손실</td>
      <td>diffusion (joint 직접)</td>
      <td>marginal fCRPS</td>
    </tr>
  </tbody>
</table>

<h3 id="autoregressive-학습--직전-글과의-연결">Autoregressive 학습 — 직전 글과의 연결</h3>

<p>FGN은 먼저 single-step 손실로 학습한 뒤, 마지막 단계에서 AR rollout finetuning을 한다. 매 학습 스텝에서 모델이 여러 스텝을 autoregressive하게 굴리고, 손실을 전체 rollout에 평균하며, gradient를 rollout 전체로 역전파한다. rollout은 최대 8 스텝까지. FGN 규모(180M×4, 0.25°)에서 8 스텝이라는 숫자는, BPTT의 메모리 한계가 어디쯤인지를 보여주는 실측치이기도 하다. 흥미논문은 AR 학습이 도움은 되지만 필수는 아니라고 못 박는다 — AR 없이도 skillful joint가 나온다는 것. joint 구조의 공로가 AR rollout이 아니라 §5의 inductive bias에 있음을 강조하는 대목이다.</p>

<h3 id="데이터">데이터</h3>

<p>ERA5로 사전학습(2016년 단일 NWP 기반의 대규모 재분석), HRES-fc0로 미세조정(2016~현재 운영 NWP 분석, 실시간 가용). 2022년을 검증, 2023년을 테스트로 동결 평가했다.</p>

<hr />

<h2 id="7-결과">7. 결과</h2>

<ul>
  <li>종합 성능: deterministic·probabilistic 지표 벤치마크 전반에서 GenCast와 ENS를 포괄적으로 추월. 보정(calibration)은 기존 모델과 비슷하거나 더 낫고, 극한값 예측도 동등 이상.</li>
  <li>Joint 구조: 검토한 대부분의 경우에서 더 나은 공간 상관구조. (marginal로만 학습했다는 점을 생각하면 이게 논문의 자랑이다.)</li>
  <li>열대 저기압 경로: 평균 궤적과 경로 확률 모두에서 유의하게 우수(p&lt;0.05).</li>
  <li>효율: 단일 TPU v5p에서 15일 예측 1개를 1분 미만에 생성하며, 앙상블 멤버는 병렬 생성. 더 큰 모델이고 6시간 step이라 프레임이 2배인데도 GenCast 대비 8배 빠르다. 이유는 단순하다 — diffusion의 반복 refinement 없이 step당 forward pass 한 번이면 끝나기 때문.</li>
  <li>WeatherNext 2 차원에서는 이전 WeatherNext를 변수·리드타임의 99.9% 에서 능가한다고 보고된다.</li>
</ul>

<hr />

<h3 id="참고-문헌">참고 문헌</h3>

<ul>
  <li>Alet, F., Price, I., El-Kadi, A., Masters, D., Markou, S., Andersson, T. R., Stott, J., Lam, R., Willson, M., Sanchez-Gonzalez, A., &amp; Battaglia, P. (2025). <em>Skillful joint probabilistic weather forecasting from marginals.</em> arXiv:2506.10772. <a href="https://arxiv.org/abs/2506.10772">https://arxiv.org/abs/2506.10772</a></li>
  <li>Price, I. et al. (2025). <em>GenCast: Diffusion-based ensemble forecasting for medium-range weather.</em> Nature. — diffusion 기반 baseline</li>
  <li>Lang, S. et al. (2024). <em>AIFS-CRPS.</em> — conditional norm 기반 stochasticity(위치별 조건)</li>
  <li>Lakshminarayanan, B. et al. (2017). <em>Simple and scalable predictive uncertainty estimation using deep ensembles.</em> NeurIPS. — epistemic 처리</li>
  <li>Zamo, M., &amp; Naveau, P. (2018). <em>Estimation of the Continuous Ranked Probability Score with limited information.</em> — fair CRPS</li>
  <li>Google DeepMind (2025). <em>WeatherNext 2.</em> <a href="https://blog.google/innovation-and-ai/models-and-research/google-deepmind/weathernext-2/">https://blog.google/innovation-and-ai/models-and-research/google-deepmind/weathernext-2/</a></li>
</ul>]]></content><author><name>Jeong-Hwan Kim</name><email>spikio19@naver.com</email></author><summary type="html"><![CDATA[요약]]></summary></entry><entry><title type="html">AI 기상 예측 모델의 multi-step rollout 학습 전략 — BPTT, Replay Buffer, Online Replay Buffer</title><link href="https://jeonghwan723.github.io/test/" rel="alternate" type="text/html" title="AI 기상 예측 모델의 multi-step rollout 학습 전략 — BPTT, Replay Buffer, Online Replay Buffer" /><published>2026-06-24T00:00:00+00:00</published><updated>2026-06-24T00:00:00+00:00</updated><id>https://jeonghwan723.github.io/test</id><content type="html" xml:base="https://jeonghwan723.github.io/test/"><![CDATA[<h2 id="1-왜-multi-step-학습이-필요한가">1. 왜 multi-step 학습이 필요한가</h2>

<p>데이터 기반 기상·기후 모델은 대부분 autoregressive(AR) 구조다. 모델 $f_\theta$ 는 한 시점의 상태 $x_t$ 를 받아 다음 시점 $x_{t+1}$ 을 예측하고, 예측을 다시 입력으로 넣어 미래로 굴러간다.</p>

\[\hat{x}*{t+k} = \underbrace{f*\theta \circ f_\theta \circ \cdots \circ f_\theta}_{k\ \text{회}}(x_t)\]

<p>문제는 학습과 추론의 <strong>불일치</strong>다. 가장 단순한 학습 방식인 single-step teacher forcing은 항상 <em>정답</em> 상태 $x_t^{\text{true}}$ 를 입력으로 받아 one-step loss만 최소화한다.</p>

\[\mathcal{L}*{\text{1-step}} = \big| f*\theta(x_t^{\text{true}}) - x_{t+1}^{\text{true}} \big|\]

<p>하지만 추론 시에는 모델이 자기 자신의 (조금씩 틀린) 출력을 입력으로 받는다. 모델은 한 번도 본 적 없는 분포의 입력을 마주하게 되고, 그 위에서 다시 예측하면서 오차가 누적된다. 결과적으로 스펙트럼이 뭉개지거나(blurring), 며칠 뒤 롤아웃이 발산하는 현상이 나타난다. 이 <strong>distribution shift</strong>가 AR 모델 불안정성의 핵심이다.</p>

<p>Multi-step rollout 학습의 목표는 한 문장으로 요약된다.</p>

<blockquote>
  <p>학습 중에 모델이 <strong>자기 자신의 출력 분포</strong>를 미리 경험하게 만들어, 추론 시 분포와 학습 시 분포를 맞춘다.</p>
</blockquote>

<p>이 목표를 어떻게 구현하느냐에 따라 전략이 갈린다. 핵심 축은 세 가지다.</p>

<ul>
  <li><strong>Gradient horizon</strong> — gradient가 롤아웃을 얼마나 거슬러 올라가 보는가</li>
  <li><strong>On-policy vs off-policy</strong> — 모델이 보는 입력 상태가 <em>현재</em> 파라미터로 만든 것인가, <em>과거</em> 파라미터로 만든 것인가</li>
  <li><strong>메모리·연산 비용</strong> — 그래프에 몇 step을 올리고, 별도 저장공간을 얼마나 쓰는가</li>
</ul>

<p>이제 세 전략을 이 축들 위에서 비교해 보자.</p>

<hr />

<h2 id="2-bptt-backpropagation-through-time">2. BPTT (Backpropagation Through Time)</h2>

<h3 id="아이디어">아이디어</h3>

<p>가장 직관적인 방법이다. 모델을 $K$ step 펼쳐서(unroll) 전체 롤아웃을 하나의 계산 그래프로 만들고, 각 step의 loss를 합한 뒤 <strong>전체 그래프를 거슬러 backprop</strong>한다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x</span> <span class="o">=</span> <span class="n">x0</span>
<span class="n">loss</span> <span class="o">=</span> <span class="mf">0.0</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">K</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
    <span class="n">x</span> <span class="o">=</span> <span class="n">f_theta</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>                 <span class="c1"># 그래프에 그대로 유지
</span>    <span class="n">loss</span> <span class="o">+=</span> <span class="n">criterion</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">target</span><span class="p">[</span><span class="n">k</span><span class="p">])</span>
<span class="n">loss</span><span class="p">.</span><span class="n">backward</span><span class="p">()</span>                    <span class="c1"># K step 전체를 거슬러 미분
</span>
</code></pre></div></div>

<h3 id="특징">특징</h3>

<p>gradient가 롤아웃 전체를 본다는 게 결정적 장점이다. 모델은 “지금 이 예측이 <em>몇 step 뒤</em> loss에 어떤 영향을 주는가”까지 고려해서 파라미터를 갱신한다. 즉, 미래까지 안정적으로 굴러가는 표현을 직접 학습한다. multi-step 신호의 품질 면에서는 사실상 gold standard다.</p>

<p>대신 대가가 크다.</p>

<ul>
  <li><strong>메모리가 $K$ 에 선형 비례한다.</strong> backprop을 위해 모든 중간 step의 activation을 들고 있어야 한다. 고해상도 전지구 필드에서는 $K$ 를 키우는 순간 GPU 메모리가 터진다. → <strong>gradient checkpointing</strong>으로 일부 activation을 버리고 backward에서 재계산하는 방식이 거의 필수다(메모리↓, 연산↑ 트레이드오프).</li>
  <li><strong>Gradient pathology.</strong> 긴 체인을 거치면 vanishing/exploding gradient가 생기고 loss 표면이 거칠어진다. 그래서 보통 작은 $K$(2~12 step 수준)에 머문다.</li>
  <li><strong>연산 비용</strong>도 update당 $K$ 회 forward + backward로 비싸다.</li>
</ul>

<h3 id="실제-사례">실제 사례</h3>

<p>GraphCast는 처음부터 긴 롤아웃으로 학습하지 않는다. <strong>curriculum</strong> 방식으로, 1 step부터 시작해 롤아웃 길이를 점진적으로 늘려 가며(최종적으로 12 step 수준까지) 짧은 fine-tuning 단계를 거친다. BPTT의 메모리·안정성 한계를 curriculum과 checkpointing으로 우회한 전형적인 예다.</p>

<blockquote>
  <p><strong>요약</strong>: gradient 신호는 가장 풍부하지만 메모리 $O(K)$ 와 gradient 불안정 때문에 긴 horizon으로 확장하기 어렵다.</p>
</blockquote>

<hr />

<h2 id="3-replay-buffer">3. Replay Buffer</h2>

<h3 id="아이디어-1">아이디어</h3>

<p>BPTT의 메모리 문제는 “전체 롤아웃을 그래프에 올린다”는 데서 온다. Replay buffer는 발상을 뒤집는다.</p>

<blockquote>
  <p>그래프에는 <strong>딱 한 step만</strong> 올린다. 대신, <em>입력 상태 자체</em>를 모델이 이미 여러 번 굴려 만든 예측으로 채워서 distribution shift를 흉내 낸다.</p>
</blockquote>

<p>이는 Brandstetter et al.(2022)의 <strong>pushforward trick</strong>과 강화학습의 replay buffer 개념을 결합한 형태다. 버퍼에는 다양한 롤아웃 깊이(depth)의 예측 상태들이 쌓여 있다. 매 iteration마다 버퍼에서 상태 하나를 뽑아 <strong>한 step만</strong> 전진시키고, 그 한 step에 대해서만 backprop한 뒤(입력에는 stop-gradient), 새로 만든 예측을 다시 버퍼에 밀어넣는다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">buffer</span><span class="p">.</span><span class="n">init_from_data</span><span class="p">()</span>            <span class="c1"># depth 0 상태들로 초기화
</span>
<span class="k">for</span> <span class="n">it</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_iters</span><span class="p">):</span>
    <span class="n">x</span><span class="p">,</span> <span class="n">depth</span> <span class="o">=</span> <span class="nb">buffer</span><span class="p">.</span><span class="n">sample</span><span class="p">()</span>     <span class="c1"># 과거에 만들어진 예측 상태
</span>    <span class="n">x_next</span> <span class="o">=</span> <span class="n">f_theta</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>            <span class="c1"># 단 1 step만 그래프에 올림
</span>    <span class="n">loss</span> <span class="o">=</span> <span class="n">criterion</span><span class="p">(</span><span class="n">x_next</span><span class="p">,</span> <span class="n">target</span><span class="p">[</span><span class="n">depth</span> <span class="o">+</span> <span class="mi">1</span><span class="p">])</span>
    <span class="n">loss</span><span class="p">.</span><span class="n">backward</span><span class="p">()</span>                <span class="c1"># 1-step gradient (x는 detach 상태)
</span>
    <span class="k">if</span> <span class="n">depth</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">&lt;</span> <span class="n">max_depth</span><span class="p">:</span>
        <span class="nb">buffer</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">x_next</span><span class="p">.</span><span class="n">detach</span><span class="p">(),</span> <span class="n">depth</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>   <span class="c1"># 한 step 더 깊은 상태로 재투입
</span>    <span class="k">else</span><span class="p">:</span>
        <span class="nb">buffer</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">sample_fresh_data</span><span class="p">(),</span> <span class="mi">0</span><span class="p">)</span>       <span class="c1"># 깊이 한계 도달 시 리셋
</span>
</code></pre></div></div>

<p>버퍼에 한 번 들어간 상태는 다음에 뽑힐 때 또 한 step 깊어지므로, 시간이 지나면 임의의 깊이까지 도달한다. 매 update의 메모리는 항상 1 step 수준으로 일정하다.</p>

<h3 id="특징-1">특징</h3>

<ul>
  <li><strong>Update당 메모리가 상수</strong>다. 그래프에 1 step만 올리니 $K$ 와 무관하게 깊은 롤아웃 경험을 줄 수 있다. BPTT가 못 가는 horizon을 메모리 부담 없이 커버한다.</li>
  <li><strong>다양한 깊이의 분포를 자연스럽게 섞어</strong> 모델에 노출시킨다.</li>
</ul>

<p>하지만 두 가지 본질적 약점이 있다.</p>

<ul>
  <li><strong>Off-policy / staleness.</strong> 버퍼의 상태는 <em>과거 버전의</em> 파라미터 $\theta_{\text{old}}$ 로 만들어진 것이다. 학습이 진행될수록 현재 모델 $\theta$ 와 버퍼 상태를 만든 모델 사이의 괴리가 커진다. 모델이 실제로 추론 시 만들 분포와, 버퍼가 담고 있는 분포가 어긋나는 것이다. RL의 off-policy 문제와 정확히 같은 구조다.</li>
  <li><strong>저장 비용과 부기(bookkeeping).</strong> 고해상도 필드 상태를 다수 보관해야 하므로 CPU/GPU 메모리를 따로 먹는다. 또 깊이 분포가 버퍼 동역학에 의해 <em>암묵적으로</em> 결정되어, “롤아웃 깊이 분포를 이렇게 하고 싶다”고 직접 지정하기 어렵다.</li>
</ul>

<blockquote>
  <p><strong>요약</strong>: 메모리 상수로 깊은 horizon을 커버하지만, 버퍼 상태가 stale(off-policy)하고 저장공간·깊이 제어가 까다롭다.</p>
</blockquote>

<hr />

<h2 id="4-online-replay-buffer">4. Online Replay Buffer</h2>

<h3 id="아이디어-2">아이디어</h3>

<p>Replay buffer의 가장 큰 흠은 staleness였다. 그렇다면 <strong>버퍼를 영구히 들고 가지 말고, 매번 현재 파라미터로 즉석에서 롤아웃을 만들면</strong> 되지 않을까? 이것이 online replay buffer의 발상이다.</p>

<p>매 iteration마다 정답 상태에서 출발해 <strong>현재 모델</strong>로 $k-1$ step을 <code class="language-plaintext highlighter-rouge">no_grad</code>로 굴리고(그래프에 안 올림), 마지막 한 step만 그래프에 올려 backprop한다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">it</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_iters</span><span class="p">):</span>
    <span class="n">x</span> <span class="o">=</span> <span class="n">sample_fresh_data</span><span class="p">()</span>
    <span class="n">k</span> <span class="o">=</span> <span class="n">sample_rollout_depth</span><span class="p">()</span>         <span class="c1"># 예: 1..K 중 랜덤
</span>
    <span class="k">with</span> <span class="n">torch</span><span class="p">.</span><span class="n">no_grad</span><span class="p">():</span>              <span class="c1"># 현재 가중치, 그래프 X
</span>        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">k</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
            <span class="n">x</span> <span class="o">=</span> <span class="n">f_theta</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>

    <span class="n">x_next</span> <span class="o">=</span> <span class="n">f_theta</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>                <span class="c1"># 마지막 1 step만 그래프에 올림
</span>    <span class="n">loss</span> <span class="o">=</span> <span class="n">criterion</span><span class="p">(</span><span class="n">x_next</span><span class="p">,</span> <span class="n">target</span><span class="p">[</span><span class="n">k</span><span class="p">])</span>
    <span class="n">loss</span><span class="p">.</span><span class="n">backward</span><span class="p">()</span>                    <span class="c1"># 1-step gradient
</span>
</code></pre></div></div>

<p>여기서 “버퍼”는 영구 저장소가 아니라 매 step 새로 생성되고 버려지는 임시적 개념이다. 사실상 pushforward trick을 임의 깊이 $k$ 로 일반화한 형태로 볼 수 있다.</p>

<h3 id="특징-2">특징</h3>

<ul>
  <li><strong>On-policy.</strong> $k-1$ step의 사전 롤아웃을 <em>현재</em> $\theta$ 로 만들기 때문에, 모델이 보는 입력 분포가 실제 추론 시 분포와 일치한다. classic replay buffer의 staleness가 사라진다.</li>
  <li><strong>메모리는 여전히 1 step.</strong> <code class="language-plaintext highlighter-rouge">no_grad</code> 구간은 activation을 저장하지 않으니, 그래프 메모리는 BPTT와 달리 $k$ 와 무관하게 작다.</li>
  <li><strong>별도 저장공간이 없다.</strong> 버퍼를 들고 다니지 않으므로 RB의 CPU/GPU 저장 부담이 없다.</li>
  <li><strong>깊이 $k$ 를 명시적으로 샘플링</strong>할 수 있어, 롤아웃 깊이 분포를 직접 통제하기 쉽다.</li>
</ul>

<p>대신 trade-off는 <strong>연산</strong>이다. 매 iteration마다 $k-1$ 번의 forward(비록 no_grad라 가볍지만)를 새로 돌려야 한다. 깊은 $k$ 를 자주 뽑으면 RB처럼 한 번 만든 상태를 재사용하지 못하고 매번 다시 굴리는 비용이 든다.</p>

<blockquote>
  <p><strong>요약</strong>: 매 step 현재 모델로 롤아웃을 즉석 생성 → on-policy + 저장공간 0 + 깊이 직접 제어. 대가는 매 iteration의 추가 forward 연산.</p>
</blockquote>

<h3 id="실제-사례-coaf">실제 사례: COAF</h3>

<p>이 전략이 단순한 학습 트릭을 넘어 <strong>결합(coupled) Earth system 모델의 안정성</strong>에 직접 기여한 사례가 COAF(Coupled Ocean-Atmosphere Framework, Chen et al., 2025)다. COAF는 사전학습된 대기·해양 모델을 spectral transformation 기반 coupler로 동적으로 결합하는 구조인데, 두 가지 핵심 기여 중 하나로 바로 <strong>online replay buffer 메커니즘</strong>을 내세운다.</p>

<p>결합 모델에서 대기와 해양은 시간 스케일이 크게 다르고, 두 컴포넌트가 서로의 (틀린) 예측을 주고받으며 굴러가기 때문에 long-range 롤아웃에서 오차가 더 쉽게 증폭된다. COAF는 online replay buffer로 모델이 자기 자신의 결합 롤아웃 분포를 on-policy로 경험하게 하여 장기 안정성을 끌어올리고, 10일 이상 예측에서 Z500·T2m의 latitude-weighted RMSE를 약 10% 낮췄다고 보고한다. single-step teacher forcing만으로는 결합 시스템의 누적 오차를 잡기 어렵다는 점을 보여주는, 본 글의 §4 전략에 대한 구체적 응용 예다.</p>

<blockquote>
  <p><strong>참고</strong>: COAF는 ICLR 2026에 제출되었다가 <strong>철회된(withdrawn)</strong> 원고다. 공개 게시 시에는 정식 게재 논문이 아니라 OpenReview preprint(withdrawn submission)로 인용하는 것이 정확하다.</p>
</blockquote>

<hr />

<h2 id="5-한눈에-비교">5. 한눈에 비교</h2>

<table>
  <thead>
    <tr>
      <th>기준</th>
      <th>BPTT</th>
      <th>Replay Buffer</th>
      <th>Online Replay Buffer</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Gradient horizon</td>
      <td>$K$ step 전체</td>
      <td>1 step</td>
      <td>1 step</td>
    </tr>
    <tr>
      <td>On/off-policy</td>
      <td>on-policy</td>
      <td><strong>off-policy (stale)</strong></td>
      <td>on-policy</td>
    </tr>
    <tr>
      <td>그래프 메모리</td>
      <td>$O(K)$ (높음)</td>
      <td>$O(1)$</td>
      <td>$O(1)$</td>
    </tr>
    <tr>
      <td>별도 저장공간</td>
      <td>없음</td>
      <td><strong>버퍼 저장 필요</strong></td>
      <td>없음</td>
    </tr>
    <tr>
      <td>Update당 연산</td>
      <td>$K$× fwd+bwd</td>
      <td>1× fwd+bwd</td>
      <td>$k$× fwd ($k{-}1$은 no_grad) + 1× bwd</td>
    </tr>
    <tr>
      <td>깊이 분포 제어</td>
      <td>명시적(but 짧음)</td>
      <td>암묵적</td>
      <td><strong>명시적</strong></td>
    </tr>
    <tr>
      <td>핵심 한계</td>
      <td>메모리·gradient 불안정</td>
      <td>staleness·저장비용</td>
      <td>깊은 $k$ 의 재계산 비용</td>
    </tr>
  </tbody>
</table>

<p>직관적으로 정리하면 이렇다.</p>

<ul>
  <li><strong>BPTT</strong>는 “gradient가 미래를 본다”는 품질을 위해 메모리를 지불한다.</li>
  <li><strong>Replay Buffer</strong>는 깊은 롤아웃을 싸게 재사용하지만 그 대가로 입력이 낡는다(off-policy).</li>
  <li><strong>Online Replay Buffer</strong>는 낡음을 없애는 대신 매번 다시 굴리는 연산을 지불한다.</li>
</ul>

<p>세 전략 모두 single-step teacher forcing보다 AR 안정성을 크게 개선하지만, 어느 것도 “공짜 점심”은 아니다. 각자 <strong>메모리 ↔ 연산 ↔ on-policy성 ↔ 깊이 제어</strong> 사이의 다른 지점에 서 있을 뿐이다.</p>

<hr />

<hr />

<h3 id="참고-문헌">참고 문헌</h3>

<ul>
  <li>Brandstetter, J., Worrall, D., &amp; Welling, M. (2022). <em>Message Passing Neural PDE Solvers.</em> ICLR. — pushforward trick</li>
  <li>Chen, K., Ling, F., Fei, B., Han, T., Wang, S., Liu, L., Li, B., &amp; Bai, L. (2025). <em>Bridging Oceans and Atmosphere: A More Comprehensive Weather Model (COAF).</em> OpenReview, ICLR 2026 withdrawn submission. <a href="https://openreview.net/forum?id=ybLoYEO9PF">https://openreview.net/forum?id=ybLoYEO9PF</a> — online replay buffer 기반 결합 모델</li>
  <li>Lam, R. et al. (2023). <em>Learning skillful medium-range global weather forecasting (GraphCast).</em> Science. — autoregressive fine-tuning curriculum</li>
  <li>McCabe, M. et al. (2023). <em>Towards Stability of Autoregressive Neural Operators.</em> — AR 안정성 분석</li>
</ul>]]></content><author><name>Jeong-Hwan Kim</name><email>spikio19@naver.com</email></author><summary type="html"><![CDATA[1. 왜 multi-step 학습이 필요한가]]></summary></entry></feed>