Skip to content
第8回:時間の発見 ~一撃からの脱却~

第8回:時間の発見 ~一撃からの脱却~

注意事項

  • 残差接続を「多様体上の流れ」と見る解釈は、連続時間極限での比喩的理解であり、実際のResNetは離散的なステップである。
  • Neural ODEの連続時間解釈は理論的に厳密だが、実装上は離散化が必要であり、計算コストと精度のトレードオフがある。
  • 「空間が安定すると時間発展が扱える」は経験的観察であり、理論的保証ではない。
  • 本回で扱う「時間」は、物理的な時間ではなく、生成や推論の「プロセスの進行度」を指す。

導入:召喚術から映画制作へ

これまでの講義では、表現空間の「形」を議論してきた。球面、双曲空間、分布としての埋め込み。しかし、これらはすべて静的な描像である。星空のスナップショットは撮れるが、星の動きを追うことはできない。

深層学習の歴史を振り返ると、興味深い転換点がある。初期の生成モデル(GAN、VAE)は、潜在空間から出力空間への一撃の変換を行っていた。魔法使いが呪文を唱えると、何もないところから画像が「召喚」される。中間過程は見えない。

しかし、近年の主流は異なる。拡散モデルは、ノイズから徐々に意味が立ち上がるプロセスを明示的に扱う。Chain of Thoughtは、推論の過程を軌跡のように見える形で言語化する。なぜ、このパラダイムシフトが起きたのか。

NOTE

Chain of Thoughtと「軌跡」: Chain of Thoughtが出力するのはテキスト列であり、モデル内部の連続状態の軌道そのものではない。また、出力されたテキストがモデル内部で実際に行われた計算を完全に反映するとは限らない。しかし、「中間過程を明示的に表現する」という点で、一撃変換からの脱却という文脈では類似の方向性を持つ。

本回では、この「時間の発見」を幾何学的に探求する。鍵となるのは、空間の安定化時間発展の制御を可能にしたと解釈できるという洞察である。

古典的生成モデルの構造と限界

GANの構造

GAN(Generative Adversarial Network)(Goodfellow et al., 2014)は、生成モデルの革命だった。その構造は驚くほどシンプルである。

zRdGxRH×W×C

潜在空間からサンプリングされた z が、ニューラルネットワーク G を一度通過するだけで、高次元の画像が生成される。識別器 D との敵対的学習により、 G は「本物らしい」画像を生成するよう訓練される。

要素役割
潜在ベクトル zランダムな「種」
生成器 G種を画像に変換する「召喚術」
識別器 D本物と偽物を見分ける「審判」

VAEの構造

VAE(Variational Autoencoder)(Kingma & Welling, 2014)は、確率的な枠組みを導入した。

xエンコーダq(zx)サンプリングデコーダx^

エンコーダは入力を潜在分布のパラメータ(平均と分散)に変換し、そこからサンプリングした z をデコーダが再構成する。KLダイバージェンスによる正則化が、潜在空間の構造を整える。

一撃変換の限界

これらのモデルは画期的だったが、共通の限界を持っていた。

中間過程の不可視性z から x への変換は、ブラックボックスの中で一気に起こる。途中で「どこまで生成が進んだか」を確認したり、「この方向に修正したい」と介入したりすることが難しい。

学習の不安定性:特にGANは、生成器と識別器のバランスが崩れやすく、モード崩壊(特定のパターンしか生成しなくなる)や学習の発散が頻繁に起きた。

制御の困難さ:「目を少し大きく」「背景を変えて」といった細かい制御が難しい。潜在空間の構造が不明瞭で、どの方向に動けば何が変わるかが分からない。

NOTE

GANの改良の歴史: GANの不安定性に対しては、多くの改良が提案された。DCGAN(畳み込みの導入)、WGAN(Wasserstein距離)、StyleGAN(スタイルベースの生成)など。これらの改良は、暗黙的に「空間の安定化」と「プロセスの制御」を追求していたと見ることもできる。

不安定性の幾何学的理解

一撃変換の限界、特に学習の不安定性は、幾何学的な観点から理解できる。

ノルムの暴走

第2回で議論したように、正規化されていない空間では、ベクトルのノルムが学習中に暴走しやすい。

生成器の中間層で h が爆発すると:

  • 勾配も爆発し、学習が発散する
  • 活性化関数が飽和し、勾配が消失する
  • 出力が極端な値に偏り、多様性が失われる

このノルムの不安定性は、「滑らかな軌道」を描くことを困難にする。

軌道としての生成

生成を「潜在空間から出力空間への軌道」として捉えてみよう。

理想的には、 z から x への変換は、何らかの多様体上の滑らかな曲線であってほしい。しかし、ノルムが暴れる空間では、この曲線がギザギザになったり、突然ジャンプしたりする。

空間の状態軌道の性質結果
不安定(ノルム暴走)ギザギザ、不連続学習困難、制御不能
安定(正規化済み)滑らか、連続学習容易、制御可能

IMPORTANT

因果関係の注意: 「空間の安定化」→「軌道の制御可能性」という関係は、経験的に観察されるパターンであり、厳密な数学的定理ではない。実際には、アーキテクチャ、損失関数、最適化手法など、複数の要因が絡み合っている。

転換点:安定化技術の成熟

残差接続(ResNet)

残差接続(He et al., 2016)は、深層学習の安定性を劇的に改善した。

ht+1=ht+f(ht)

従来の理解では、これは「勾配消失問題の緩和」として説明される。スキップ接続により、勾配が直接浅い層に伝播できる。

しかし、幾何学的には別の解釈も可能である。

残差接続の幾何学的再解釈

残差接続を、多様体上の流れの離散近似として読み直そう。

  • ht :現在の状態(多様体上の点)
  • f(ht) :接空間上のベクトル場(「どの方向に動くべきか」)
  • ht+1=ht+f(ht) :オイラー法による1ステップの更新

NOTE

ベクトル場とベクトル値関数の違い: f はベクトルを返す関数なので「ベクトル値関数」と呼ぶこともできる。しかし「ベクトル場」と呼ぶとき、強調したいのは空間のすべての点にベクトル(矢印)が対応しているという大域的な構造である。天気図の風向図や川の流速分布をイメージするとよい。「この地点に来たら、どの方向へ、どのくらいの速さで進むか」という指示書が、空間全体に敷き詰められている状態である。単なる写像(入力を一撃で出力に変換するテレポート)とは異なり、ベクトル場は局所的な「押し出し方向」を累積することで変換を実現するという点が本質的に異なる。

この見方では、ResNetの各層は「時間」の1ステップに対応する。層を深くすることは、時間を長く進めることに相当する。

txt
層1 → 層2 → 層3 → ... → 層L
 ↓      ↓      ↓           ↓
t=0 → t=1 → t=2 → ... → t=L

NOTE

比喩の限界: この「時間」は物理的な時間ではなく、計算の「深さ」を時間軸に見立てた比喩である。実際のResNetは離散的であり、連続時間のODEとは異なる。しかし、この比喩はNeural ODEへの橋渡しとして有用である。

スキップ接続の役割

スキップ接続 ht+1=ht+f(ht) において、 ht の項は何を意味するか。

記憶保持機構:入力の情報を、変換を経ても保持する。 f(ht) が何をしようと、 ht の成分は残る。

恒等写像への近さf(ht)0 のとき、 ht+1ht となる。つまり、「何もしない」という選択肢が常にある。これにより、深い層でも浅い層の情報が失われにくい。

安定性の源f の出力が小さければ、状態の変化も小さい。これが、深いネットワークでも学習を安定させる一因となる。

正規化層の役割

残差接続と並んで重要なのが、正規化層である。

Batch Normalization(Ioffe & Szegedy, 2015):ミニバッチ内で平均・分散を正規化。

Layer Normalization(Ba et al., 2016):各サンプル内で正規化。Transformerで広く使用。

RMSNorm(Zhang & Sennrich, 2019):平均を引かず、二乗平均平方根で割る。LLMで採用例多数。

これらの正規化は、活性化のスケールを一定範囲に保つ。ノルムの暴走を防ぎ、空間を「安定」させる。

正規化手法正規化の軸主な用途
Batch Normバッチ方向CNN
Layer Norm特徴方向Transformer
RMSNorm特徴方向(平均なし)大規模LLM

CAUTION

LayerNormとL2正規化の違い: 第6回でも強調したが、LayerNorm/RMSNormは平均・分散の正規化であり、ベクトルを単位ノルムにするL2正規化とは異なる。LayerNormを通過したベクトルは、一般に単位ノルムではない。

概念整理:写像からベクトル場へ

ResNetの安定化技術を踏まえて、Neural ODEへ進む前に、これまで登場したモデルを「写像」と「ベクトル場」という観点で整理しておく。両者とも関数 f を用いるが、それをどう反復(離散の場合)または積分(連続の場合)してプロセスを定義するかが根本的に異なる。

1. 写像としての解釈(GANやVAEなど)

  • 数式イメージ: y=f(x)
  • 出力の意味:「移動先の座標」 そのもの。
  • 直感イメージ:「テレポート」。入力 x を、関数が一撃で別の地点 y へワープさせる。始点と終点の関係のみが重要であり、その間の連続的な変化(軌跡)は明示されない(ブラックボックスとなる)。(※VAEは確率的なサンプリングを含むが、逐次積分を行わず「一回の処理」で生成する点でこちらに分類される。)

2. ベクトル場としての解釈(ResNetやNeural ODE、拡散モデル)

  • 数式イメージ: xt+1=xt+Δtf(xt,t) (離散) または dxdt=f(x,t) (連続)
  • 出力の意味:「その地点での更新方向(離散では更新量、連続では速度)」
  • 直感イメージ:「海流」。入力 x は、その地点にある矢印に従って「少しずつ押し流される」。微小な変化を足し合わせ続ける(積分・反復する)ことで、初めて移動というプロセスが生じる。
    • ResNetはステップ幅 Δt=1 と置いた更新則とみなせる。
    • 拡散モデルは確率過程(SDE)として定式化されるが、生成は反復・積分として実装できるためベクトル場的に解釈できる。

ResNetが残差を学習することや、拡散モデルがノイズ除去の「方向」を学習して徐々に画像を生成するのは、ネットワークに「完成品(座標)」を直接作らせるのではなく、「状態をどう変化させるべきかというルール(ベクトル場)」 を学習させているといえる。

Neural ODE:連続時間への拡張

離散から連続へ

ResNetの残差接続を、時間ステップ Δt=1 のオイラー法と見なすと:

ht+Δt=ht+Δtf(ht)

Δt0 の極限を取ると、常微分方程式(ODE)が得られる:

dhdt=f(h,t,θ)

つまり、ResNetは微分方程式の離散近似といえる。 Neural ODE(Chen et al., 2018)は、この連続時間の枠組みを直接扱う。

Neural ODEとは何か:具体的なイメージ

数式の導出を追う前に、Neural ODEが「何をするものか」を平易な言葉で押さえておく。

一言で言えば:「ニューラルネットワークが定義したベクトル場(=速度の地図)に従って、状態を時刻 t=0 から t=T まで流し続けるモデル」である。

h(0)(1)h(t)(2)h(T)

ここで、 (1) はベクトル場 f(h,t,θ) に従った時間発展、 (2) は ODE solver による積分を表す。

この f だけがニューラルネットワークである。 ODEソルバーが h を少しずつ押し流す。

従来のニューラルネットワーク(ResNetなど)との違いは、変換の粒度にある。

ResNetは「層」という固定された離散ステップで状態を変換する。シェフが決まった工程数で料理を仕上げるようなものだ。工程を増やしたければ、材料(パラメータ)を増やすしかない。

Neural ODEは違う。ニューラルネットワーク f が定義するのは「今の状態 h から、次の瞬間にどの方向へどれだけ動くか」という微小な変化の規則だけである。その規則を積分し続けることで、最終的な出力 h(T) が得られる。

txt
【ResNetの感覚】
  "6ステップで変換する" → 6層分のパラメータが必要

【Neural ODEの感覚】
  "この速度場に従って、t=0からt=1まで流す" → 速度場のパラメータだけ
   ↑ 何ステップ刻むかはソルバーが自動で決める

何を学習するのか、という観点でいうと:

  • ResNetが学習するのは「各層での変換そのもの」
  • Neural ODEが学習するのは「状態空間全体に敷かれた速度の地図」——すなわちベクトル場 f(h,t,θ)

この速度の地図さえ正しく学習できれば、どの初期状態 h(0) から出発しても、流した先の h(T) が望ましい出力になる。生成モデルとして使うなら、「ランダムノイズを流し続けると、データらしい表現に到達する」という動作になる。

NOTE

f が受け取る引数について: f(h,t,θ) は状態 h と時刻 t の両方を入力として受け取る。時刻 t を入力に含めることで、「時刻によって押す方向を変える」非自律系(non-autonomous)のベクトル場を表現できる。時刻を使わない設計(自律系)はよりシンプルだが、時間依存のダイナミクスをそのまま表現できないため、問題によっては表現上の制約になる(必要なら状態拡張などで補える)。

Neural ODEの本質的な跳躍:何が革命だったのか

Neural ODE(Chen et al., 2018)は NeurIPS 2018 Best Papers の一つに選ばれ、ResNetを「微分方程式の離散化」として捉える見方を広く共有させた。 重要なのは、ニューラルネットワークと常微分方程式を同じ枠組みで扱えるようにした点である。ここから得られる洞察を三つに整理する。

洞察1:パラメータを共有した「無限深さ」

典型的なResNetでは、各層 W1,W2,,WL が異なるパラメータを持つ。深くするほどパラメータ数は増える(weight sharing を用いる構造は別として)。

Neural ODEでは、ベクトル場 f(h,t,θ) のパラメータ θ時刻によらず共有される。ODEソルバーがどれだけ細かくステップを刻もうと、使われる f は常に同じ関数である。

ResNet(各層が独立したパラメータを持つ):

h0[W1]h1[W2]h2[W3]h3

Neural ODE(パラメータ共有で、 t だけが変わる):

h(0)[f,θ][f,θ][f,θ]h(T)

時刻のイメージ: t=0.0, 0.3, 0.6

固定されたパラメータ数のまま、任意の「深さ」を持てる。モデルの深さが連続値になった瞬間である。「何層積むか」という問いは「いつまで積分するか」という問いに変わった。計算精度を上げたければ、ソルバーに細かいステップを使わせるだけでよく、パラメータを増やす必要がない。

洞察2:ODEソルバーが「層」になった

Neural ODEの積分は:

h(T)=h(0)+0Tf(h(t),t,θ),dt

この積分を計算するのは、ルンゲ=クッタ法などの既存のODEソルバーである。ニューラルネットワークの層の代わりに、数値解析ライブラリがブラックボックスとして使われる。

これは何を意味するか。外部の高精度ソルバーをそのまま深層学習のフォワードパスとして使えるようになった。精度保証・適応的なステップ幅・剛性への対応——数値解析が長年かけて磨いてきた知見が、ニューラルネットワークの計算に直接流れ込んだ。ベクトル場が急峻な領域では細かく、滑らかな領域では粗く——この「難しいところだけ細かく計算する」という動作は、固定的な層の積み重ねでは実現できない。

洞察3:勾配の計算もODEである(随伴法の本質)

最も衝撃的な洞察は、逆伝播の扱い方にある。

通常の深層学習では、逆伝播のために中間状態を保存しておく必要がある。100層のネットワークなら100個の状態を記憶しておく。Neural ODEは理論上これが不要になる。ここで別の問いを立てたのである。「勾配の計算もODEとして解けないか?」

答えはYesだった。損失 L に対する勾配の時間発展は、「随伴状態(adjoint state)」 a(t)=L/h(t) が満たすODEとして書ける:

dadt=a(t)f(h(t),t,θ)h

フォワードパスのODEを解くのと同じ仕組みで、バックワードパスも解ける。「ODEを解く」という行為が、学習の全体をひとつの統一された枠組みで包んだのである。この再帰的な美しさが、論文を読んだ研究者を唸らせた。

NOTE

実運用上のトレードオフ: 随伴法は「中間状態を全保存しない」設計が可能という意味でメモリ効率が良い。ただし、随伴法は逆時間方向にODEを解くため、順方向で安定だった軌道が逆方向では不安定になることがある。数値誤差の蓄積も問題になり、安定性のため結局中間状態を一部保存するケース(チェックポイント)も多い。実務では通常の逆伝播の方が精度が出る場合もあり、メモリと精度のトレードオフを考慮した手法選択が必要である。

Neural ODEの構造

h(0)(1)h(T),f:neural network

(1) は「 dhdt=f(h,t,θ) を満たす軌道を ODE solver で積分する」ことを表している。

f はニューラルネットワークで表現され、 θ はその学習パラメータ。ODEソルバーが、 t=0 から t=T まで状態を積分する。

Neural ODEの表現力の限界とAugmented Neural ODEs

Neural ODEには重要な制約がある。ODEの解は連続で、異なる初期条件から出発した軌道は交差しない(解の一意性)。これは、トポロジー的に表現できない関数が存在することを意味する。

典型的な例は、同心円状に配置された2クラスの分類問題である。内側の円と外側の円を分離するには、軌道が「交差」する必要があるが、ODEではこれができない。

Neural ODEの制約とAugmented Neural ODE

Augmented Neural ODEs(Dupont et al., 2019)は、この制約を回避するために状態空間の次元を拡張する。入力を高次元空間に持ち上げることで、元の空間では交差するように見える軌道も、拡張された空間では交差しないように配置できる。

haug=[h,0]Rd+d

これは、2次元で結び目を解くには3次元が必要、という直感と同じである。

NOTE

計算コストのトレードオフ: Neural ODEはメモリ効率が良いが、計算時間は増加しうる。特に、 f の評価回数(ODEソルバーのステップ数)が多くなると、標準的なResNetより遅くなる場合がある。

時間発展の幾何学

位相空間としての表現空間

力学系の言葉を借りると、表現空間は 位相空間(phase space) に対応する。各点が「状態」を表し、時間発展は位相空間内の軌道として描かれる。

生成モデルの文脈では:

  • 位相空間:潜在空間 + 中間表現の空間
  • 軌道:生成プロセスにおける状態の遷移
  • アトラクター:学習されたデータ分布

ベクトル場としてのニューラルネットワーク

Neural ODEの f(h,t,θ) は、位相空間上のベクトル場を定義する。各点 h に対して、「次にどの方向に動くべきか」を指示する矢印が割り当てられている。

学習とは、このベクトル場を調整して、望ましい軌道(例:ノイズからデータへの変換)を実現することである。

txt
      ↗ → →
    ↗   ↘
  ↗       ↓   ← ベクトル場
  ↑       ↓
  ↑     ↙
    ← ← ←

流れの保存則

連続時間の流れには、重要な性質がある。ODEの解は、初期条件が近ければ、(少なくとも短時間では)近くを通る。これは連続依存性と呼ばれる。

この性質により、潜在空間での「近さ」が、生成結果の「近さ」に反映されやすくなる。 z1z2 が近ければ、生成される x1x2 も近い。

IMPORTANT

カオスの可能性: ただし、長時間の発展では、初期条件のわずかな違いが大きな差を生む「カオス」が起こりうる。Neural ODEがカオス的になるかどうかは、ベクトル場 f の性質に依存する。

Neural ODEが開いた地平

2018年のNeural ODEの登場は、深層学習コミュニティで概念的な転換点として受け止められた。「深層ネットワークとODEが同じだった」という洞察は、その後の研究の方向性に大きな影響を与えた。その後の数年で、AI研究の地形は大きく書き換えられた。

連続正規化フロー(FFJORD)

Neural ODEの発表から数か月後、FFJORD(Grathwohl et al., 2019)が登場した。

確率密度の連続時間変化を記述するには、ベクトル場 fdivergence(ヤコビアンのトレース tr(f/h) を積分する必要がある。このトレースを厳密に求めるには、基底ベクトルを用いて対角成分を足し上げる必要があり、結果として O(d) 回程度の自動微分(JVP/VJP)が必要になるため、高次元ではスケールしなかった。

FFJORDはここにHutchinson推定量を組み合わせた。ランダムベクトルを使ったトレース推定により、divergenceを O(d) に近い計算量で見積もれるようにした。高次元空間での正確な確率密度の学習——これは、次世代の生成モデルの基盤となる問いである。

Latent ODE:時系列への展開

同じく2018〜2019年にかけて、Latent ODE(Rubanova et al., 2019)が時系列データへの応用を開拓した。

不規則な時刻に観測された時系列(医療記録、センサーデータなど)は、従来のRNNやLSTMでは扱いにくい。等間隔でないタイムスタンプに対して、モデルが固定したステップ幅を仮定するからである。

Latent ODEは、潜在空間での状態発展をODEで記述することで、任意の時刻への「補間」と「外挿」を自然に実現した。観測と観測の間で、モデルは世界の状態を連続的に追跡し続ける。

Flow Matchingへの道

Neural ODEの最も深い遺産は、Flow Matching(Lipman et al., 2022; Liu et al., 2022)として結実した。

Flow Matchingは「どんなベクトル場を学習すれば、目標の確率分布に到達できるか」という問いを、シンプルな回帰問題に変換した。拡散モデルの複雑なスコアマッチングとは異なり、直線的な軌道(単純なODE経路)を目標として設定することで、学習が大幅に安定した。近年は、Flow Matching / Rectified Flow 系の学習を採用する大規模画像生成モデルも現れており(例:Flux系、またSD3でもflow matching技術が言及される)、Neural ODEの系譜が最前線に直結している。

パラダイムの転換:何が変わったか

Neural ODEの登場前と後を比べると、研究の問い自体が変わったことが分かる。

Neural ODE以前Neural ODE以後
モデルの「深さ」整数(何層か)連続値(どこまで積分するか)
生成の問い「どんな写像を学ぶか」「どんなベクトル場を学ぶか」
確率密度の扱い変分推論(近似)連続変換(正確)
時系列の問い「どう離散化するか」「連続時間で記述できるか」
数値解析との関係別の分野深層学習の内部技術

「召喚術(一撃変換)」から「映画制作(軌道の学習)」へ。この比喩が単なる比喩ではなく、数学的に厳密な枠組みとして定式化されたことが、Neural ODEの革命の本質だった。ニューラルネットワークは「写像を近似するもの」から「流れを定義するもの」へと、その自己理解を更新した。

拡散モデルへの橋渡し

拡散モデルの直感

拡散モデル(次回詳述)は、Neural ODEの考え方をさらに発展させたものである。

基本的なアイデア:

  1. データにノイズを徐々に加えて、完全なノイズにする(順過程)
  2. このノイズ付加過程を逆転させる方法を学習する(逆過程)
  3. ランダムノイズから始めて、逆過程を適用すると、データが生成される
txt
データ x₀ → x₁ → x₂ → ... → xₜ → 純粋ノイズ
  ↑                               |
  └──── 学習した逆過程で戻る ─────┘

なぜ拡散モデルが強力か

中間過程の可視性:生成の各ステップが明示的に定義されている。 t=T から t=0 への軌跡を追跡できる。

学習の安定性:各ステップで「ノイズを少し除去する」というシンプルなタスクを学習する。GAN のような敵対的学習の不安定性がない。

制御の容易さ:中間状態に介入することで、生成を制御できる。Classifier-free guidanceなどの技術が、この性質を活用している。

時間の明示化

拡散モデルでは、「時間」 t が明示的なパラメータとして現れる。モデルは (xt,t) を入力として受け取り、「時刻 t での状態 xt から、どの方向に進むべきか」を出力する。

この構造はNeural ODEのベクトル場 f(h,t,θ) と類似している。ただし、拡散モデルの本質は 確率過程(SDE:確率微分方程式) であり、ODEではない点に注意が必要である。

NOTE

ODEとSDEの関係: 拡散モデルには「確率流ODE(probability flow ODE)」というODE表現も存在し、これを使えば決定論的なサンプリングが可能になる。しかし、学習と生成の基本的な理解はSDE、または離散時間マルコフ連鎖(DDPM等における離散化された順過程・逆過程)に基づく。次回でこの関係を詳しく扱う。

まとめ

概念定義本回での役割
一撃変換潜在空間から出力空間への直接的な写像古典的生成モデルの特徴
残差接続ht+1=ht+f(ht)時間発展の離散近似
Neural ODEdhdt=f(h,t)連続時間の深層学習
ベクトル場各点に速度ベクトルを割り当てる関数時間発展の方向を決定
軌跡位相空間内での状態の遷移生成プロセスの可視化

本回のポイント

深層学習における「時間の発見」は、二つの技術的進歩と、一つの概念的統合によって実現した。

空間の安定化:正規化層(LayerNorm、RMSNorm)と残差接続により、表現空間が安定しやすくなった。ノルムの暴走が抑えられることで、滑らかな軌道を描ける土台ができたと解釈できる。ただし、これは経験的な観察であり、「安定化すれば必ず時間発展が制御可能になる」という理論的保証ではない。

時間の明示化:残差接続を「時間ステップ」として読み直すことで、離散的な層の積み重ねが、連続的な流れの近似として理解できるようになった。Neural ODEはこの視点を厳密化し、連続時間の枠組みを提供した。

ニューラルネットワークとODEの統合:これが最も根本的な転換だった。「深層学習は写像を近似する」という自己理解から、「深層学習はベクトル場を学習し、それを積分することで変換を実現する」という理解へ。パラメータを共有した無限深さ、勾配計算のODE定式化、ODEソルバーとの統合——これらは、後のFFJORD、Latent ODE、そしてFlow Matchingへと連なる道を開いた。近年はFlow Matching / Rectified Flow 系の学習を採用する大規模画像生成モデルも現れており(Flux系など、またSD3でもflow matching技術が言及される)、2018年の論文が現在の生成AIの根幹に系譜として直結していることが分かる。

この見方は、生成モデルのパラダイム変化を理解する一つのレンズを与える。一撃の「召喚術」から、徐々に形が立ち上がる「映画制作」へ。次回では、この「映画」の具体例として、拡散モデルとSDEを詳しく見ていく。

空間が安定すると、「映画(プロセス)」が撮れるようになる。

次回予告

第9回「拡散と凝縮」では、拡散モデルとSDEの対応を詳しく見ていく。

本回で導入した「時間」の概念を、確率過程の枠組みで拡張する。ノイズから意味が立ち上がるプロセスを、熱力学的な「拡散」と「凝縮」の言葉で記述する。スコア関数、ランジュバン動力学、そしてflow matchingといった技術が、幾何学的にどう解釈できるかを探求する。

実装ノート

NOTE

以下のコードは PyTorch >= 1.9 を前提とする。Neural ODEの実装には torchdiffeq ライブラリを使用する例も示す。

残差ブロックの実装

コード例: 08_residual_block.py
python
import torch
import torch.nn as nn


class ResidualBlock(nn.Module):
    """基本的な残差ブロック

    h_{t+1} = h_t + f(h_t)

    ここで f は2層のMLPとする。
    """

    def __init__(self, dim, hidden_dim=None):
        super().__init__()
        hidden_dim = hidden_dim or dim * 4

        self.net = nn.Sequential(
            nn.LayerNorm(dim),
            nn.Linear(dim, hidden_dim),
            nn.GELU(),
            nn.Linear(hidden_dim, dim),
        )

        # 出力を小さく初期化(学習初期の安定性のため)
        nn.init.zeros_(self.net[-1].weight)
        nn.init.zeros_(self.net[-1].bias)

    def forward(self, x):
        # 残差接続: x + f(x)
        return x + self.net(x)


class ResidualStack(nn.Module):
    """残差ブロックを積み重ねたネットワーク

    「深さ」を「時間」として解釈できる。
    """

    def __init__(self, dim, num_blocks, hidden_dim=None):
        super().__init__()
        self.blocks = nn.ModuleList([ResidualBlock(dim, hidden_dim) for _ in range(num_blocks)])

    def forward(self, x, return_trajectory=False):
        """
        Args:
            x: 入力 [batch, dim]
            return_trajectory: Trueなら中間状態も返す

        Returns:
            output: 出力 [batch, dim]
            trajectory: (オプション) 軌跡 [num_blocks+1, batch, dim]
        """
        if return_trajectory:
            trajectory = [x]

        for block in self.blocks:
            x = block(x)
            if return_trajectory:
                trajectory.append(x)

        if return_trajectory:
            return x, torch.stack(trajectory)
        return x


# 使用例
dim, num_blocks = 64, 8
model = ResidualStack(dim, num_blocks)

x = torch.randn(32, dim)  # バッチサイズ32
output, trajectory = model(x, return_trajectory=True)

print(f"Input shape: {x.shape}")
print(f"Output shape: {output.shape}")
print(f"Trajectory shape: {trajectory.shape}")  # [9, 32, 64]

Neural ODEの実装(torchdiffeqを使用)

コード例: 08_neural_ode.py
python
# pip install torchdiffeq
import torch
import torch.nn as nn

try:
    from torchdiffeq import odeint

    TORCHDIFFEQ_AVAILABLE = True
except ImportError:
    TORCHDIFFEQ_AVAILABLE = False
    print("torchdiffeq not installed. Neural ODE examples will be skipped.")


class ODEFunc(nn.Module):
    """ODEの右辺 f(h, t) を定義するニューラルネットワーク

    dh/dt = f(h, t)
    """

    def __init__(self, dim, hidden_dim=None):
        super().__init__()
        hidden_dim = hidden_dim or dim * 4

        self.net = nn.Sequential(
            nn.Linear(dim, hidden_dim),
            nn.Tanh(),  # 有界な活性化関数(滑らかさの促進に寄与しうるが、
            # これだけでリプシッツ連続性が保証されるわけではない)
            nn.Linear(hidden_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, dim),
        )

    def forward(self, t, h):
        """
        Args:
            t: 時刻(スカラー)
            h: 状態 [batch, dim]

        Returns:
            dh/dt: 時間微分 [batch, dim]
        """
        # この実装ではtを使わない(自律系)
        # 時間依存にするなら、tを特徴として連結する
        return self.net(h)


class NeuralODE(nn.Module):
    """Neural ODEモデル"""

    def __init__(self, dim, hidden_dim=None):
        super().__init__()
        self.func = ODEFunc(dim, hidden_dim)

    def forward(self, x, t_span=None, method="dopri5", return_trajectory=False):
        """
        Args:
            x: 初期状態 h(0) [batch, dim]
            t_span: 積分する時刻のリスト(デフォルト: [0, 1])
            method: ODEソルバー('dopri5', 'euler', 'rk4'など)
            return_trajectory: Trueなら全時刻の状態を返す

        Returns:
            output: 最終状態 h(T) [batch, dim]
            trajectory: (オプション) 全時刻の状態 [len(t_span), batch, dim]
        """
        if not TORCHDIFFEQ_AVAILABLE:
            raise RuntimeError("torchdiffeq is required for Neural ODE")

        if t_span is None:
            t_span = torch.tensor([0.0, 1.0])

        # ODEを解く
        trajectory = odeint(self.func, x, t_span, method=method)

        if return_trajectory:
            return trajectory[-1], trajectory
        return trajectory[-1]


# 使用例(torchdiffeqがインストールされている場合)
if TORCHDIFFEQ_AVAILABLE:
    dim = 64
    model = NeuralODE(dim)

    x = torch.randn(32, dim)
    t_span = torch.linspace(0, 1, 11)  # 0, 0.1, 0.2, ..., 1.0

    output, trajectory = model(x, t_span, return_trajectory=True)

    print(f"Input shape: {x.shape}")
    print(f"Output shape: {output.shape}")
    print(f"Trajectory shape: {trajectory.shape}")  # [11, 32, 64]

軌跡の可視化

コード例: 08_trajectory_visualization.py
python
import matplotlib.pyplot as plt
import numpy as np
import torch


class ResidualStack:
    def __init__(self, dim, num_blocks):
        self.dim = dim
        self.num_blocks = num_blocks

    def __call__(self, x, return_trajectory=False):
        trajectory = x.unsqueeze(0).repeat(self.num_blocks + 1, 1, 1)
        if return_trajectory:
            return x, trajectory
        return x


def visualize_trajectory_2d(trajectory, title="State Trajectory"):
    """2次元に射影した軌跡を可視化

    Args:
        trajectory: 軌跡 [time_steps, batch, dim]
        title: グラフのタイトル
    """
    # 最初の2次元に射影
    traj_2d = trajectory[:, :, :2].detach().cpu().numpy()
    time_steps, batch_size, _ = traj_2d.shape

    fig, ax = plt.subplots(figsize=(8, 8))

    # 各サンプルの軌跡をプロット
    colors = plt.cm.viridis(np.linspace(0, 1, batch_size))

    for i in range(min(batch_size, 20)):  # 最大20サンプル
        ax.plot(traj_2d[:, i, 0], traj_2d[:, i, 1], color=colors[i], alpha=0.5, linewidth=1)
        ax.scatter(
            traj_2d[0, i, 0],
            traj_2d[0, i, 1],
            color=colors[i],
            marker="o",
            s=50,
            label="Start" if i == 0 else "",
        )
        ax.scatter(
            traj_2d[-1, i, 0],
            traj_2d[-1, i, 1],
            color=colors[i],
            marker="x",
            s=50,
            label="End" if i == 0 else "",
        )

    ax.set_xlabel("Dimension 1")
    ax.set_ylabel("Dimension 2")
    ax.set_title(title)
    ax.legend()
    ax.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.savefig("trajectory_2d.png", dpi=150)
    plt.close()

    print("Saved: trajectory_2d.png")


# ResidualStackの軌跡を可視化
dim, num_blocks = 2, 20  # 2次元で直接可視化
model = ResidualStack(dim, num_blocks)

x = torch.randn(50, dim) * 2  # 50サンプル
output, trajectory = model(x, return_trajectory=True)

visualize_trajectory_2d(trajectory, "ResNet Trajectory (2D)")

ResNetとNeural ODEの比較

コード例: 08_resnet_vs_ode.py
python
import torch

TORCHDIFFEQ_AVAILABLE = False


class ResidualStack:
    def __init__(self, dim, num_steps):
        self.dim = dim
        self.num_steps = num_steps

    def __call__(self, x, return_trajectory=False):
        trajectory = x.unsqueeze(0).repeat(self.num_steps + 1, 1, 1)
        if return_trajectory:
            return x, trajectory
        return x


class NeuralODE:
    def __init__(self, dim):
        self.dim = dim

    def __call__(self, x, t_span, return_trajectory=False):
        trajectory = x.unsqueeze(0).repeat(t_span.numel(), 1, 1)
        if return_trajectory:
            return x, trajectory
        return x


def compare_resnet_and_ode(dim=64, num_steps=10):
    """ResNetとNeural ODEの挙動を比較

    ResNet: 離散的なステップ
    Neural ODE: 連続的な流れ(離散化して比較)
    """
    # ResNet
    resnet = ResidualStack(dim, num_steps)

    # Neural ODE(利用可能な場合)
    if TORCHDIFFEQ_AVAILABLE:
        neural_ode = NeuralODE(dim)

    # テスト入力
    x = torch.randn(16, dim)

    # ResNetの軌跡
    _, resnet_traj = resnet(x, return_trajectory=True)

    results = {
        "resnet": {
            "trajectory_shape": resnet_traj.shape,
            "output_norm_mean": resnet_traj[-1].norm(dim=-1).mean().item(),
            "output_norm_std": resnet_traj[-1].norm(dim=-1).std().item(),
        }
    }

    # Neural ODEの軌跡(利用可能な場合)
    if TORCHDIFFEQ_AVAILABLE:
        t_span = torch.linspace(0, 1, num_steps + 1)
        _, ode_traj = neural_ode(x, t_span, return_trajectory=True)

        results["neural_ode"] = {
            "trajectory_shape": ode_traj.shape,
            "output_norm_mean": ode_traj[-1].norm(dim=-1).mean().item(),
            "output_norm_std": ode_traj[-1].norm(dim=-1).std().item(),
        }

    return results


# 比較実行
results = compare_resnet_and_ode()
print("Comparison Results:")
for model_name, metrics in results.items():
    print(f"\n{model_name}:")
    for k, v in metrics.items():
        print(f"  {k}: {v}")

参考文献

残差接続とResNet

  • He, K., Zhang, X., Ren, S., & Sun, J. (2016). Deep Residual Learning for Image Recognition. CVPR 2016. arXiv: arXiv:1512.03385.
    • ResNetの原論文。残差接続が深いネットワークの学習を可能にすることを示した。
  • He, K., Zhang, X., Ren, S., & Sun, J. (2016). Identity Mappings in Deep Residual Networks. ECCV 2016. arXiv: arXiv:1603.05027.
    • Pre-activation ResNetの提案。残差接続の設計についてさらに分析。

正規化層

  • Ioffe, S., & Szegedy, C. (2015). Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. ICML 2015. arXiv: arXiv:1502.03167.
    • Batch Normalizationの原論文。
  • Ba, J. L., Kiros, J. R., & Hinton, G. E. (2016). Layer Normalization. arXiv:1607.06450. arXiv: arXiv:1607.06450.
    • Layer Normalizationの原論文。Transformerで広く使用される。
  • Zhang, B., & Sennrich, R. (2019). Root Mean Square Layer Normalization. NeurIPS 2019. arXiv: arXiv:1910.07467.
    • RMSNormの原論文。大規模LLMで採用例が増加。

Neural ODE

  • Chen, R. T. Q., Rubanova, Y., Bettencourt, J., & Duvenaud, D. (2018). Neural Ordinary Differential Equations. NeurIPS 2018. arXiv: arXiv:1806.07366.
    • Neural ODEの原論文。連続深度モデルと随伴法による効率的な学習を提案。NeurIPS 2018 Best Papersの一つに選出。
  • Dupont, E., Doucet, A., & Teh, Y. W. (2019). Augmented Neural ODEs. NeurIPS 2019. arXiv: arXiv:1904.01681.
    • Neural ODEの表現力の限界と、次元を拡張することによる解決策。
  • Grathwohl, W., Chen, R. T. Q., Bettencourt, J., Sutskever, I., & Duvenaud, D. (2019). FFJORD: Free-form Continuous Dynamics for Scalable Reversible Generative Models. ICLR 2019. arXiv: arXiv:1810.01367.
    • Neural ODEを正規化フローへ応用。密度変化に現れるdivergence(ヤコビアンのトレース)をHutchinson推定量でスケーラブルに推定する手法を提案。
  • Rubanova, Y., Chen, R. T. Q., & Duvenaud, D. (2019). Latent Ordinary Differential Equations for Irregularly-Sampled Time Series. NeurIPS 2019. arXiv: arXiv:1907.03907.
    • 不規則時系列データへのNeural ODEの応用。
  • Lipman, Y., Chen, R. T. Q., Ben-Hamu, H., Nickel, M., & Le, M. (2022). Flow Matching for Generative Modeling. ICLR 2023. arXiv: arXiv:2210.02747.
    • ODEベースの生成モデルの学習を回帰問題に変換。現代の高性能画像生成モデルの基盤。

古典的生成モデル

  • Goodfellow, I., Pouget-Abadie, J., Mirza, M., Xu, B., Warde-Farley, D., Ozair, S., Courville, A., & Bengio, Y. (2014). Generative Adversarial Nets. NeurIPS 2014. arXiv: arXiv:1406.2661.
    • GANの原論文。敵対的学習による生成モデル。
  • Kingma, D. P., & Welling, M. (2014). Auto-Encoding Variational Bayes. ICLR 2014. arXiv: arXiv:1312.6114.
    • VAEの原論文。変分推論による生成モデル。

拡散モデル(次回への接続)

  • Ho, J., Jain, A., & Abbeel, P. (2020). Denoising Diffusion Probabilistic Models. NeurIPS 2020. arXiv: arXiv:2006.11239.
    • DDPMの原論文。次回で詳しく扱う。