← 機械学習テキスト 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:誤差逆伝播法活性化関数(勾配消失) | 関連:最適化の実務(勾配クリッピング)

要点(BLUF)


1. 系列データはなぜ特別か

系列データとは、順序に意味があり、長さが一定でない データのことです。例:

これらが 誤差逆伝播法 で扱った普通の MLP や、画像の CNN で扱いにくいのは次の3点です。

性質MLP / CNN の困りごと
可変長入力次元を固定する MLP は、長さ5の文と長さ50の文を同じ層で扱えない
順序全結合は入力をベクトルとして一括投入するので、並び順の情報が混ざって消える
長距離の文脈文頭の主語が文末の動詞の活用に効く、といった離れた依存を全結合で表すのは非効率

要するに:「同じ処理を、過去の要約を引き継ぎながら、1ステップずつ繰り返す」 仕組みが欲しい。それが RNN です。


2. RNN の構造:隠れ状態と重み共有

RNN は1ステップごとに 隠れ状態 hth_t を更新します。

ht=ϕ ⁣(Whhht1+Wxhxt+bh)h_t = \phi\!\left(W_{hh}\,h_{t-1} + W_{xh}\,x_t + b_h\right) yt=Whyht+byy_t = W_{hy}\,h_t + b_y

要するに:hth_t は「今の入力 xtx_t直前までの要約 ht1h_{t-1}」を混ぜて作る現在の記憶。yty_t はそこからの出力。

時間方向の重み共有

最重要の性質は、どの時刻でも同じ Whh,WxhW_{hh}, W_{xh} を使う ことです。これにより、

時間展開(unfold)

再帰をほどいて並べると、深さ=系列長のフィードフォワード網に見えます。これが学習(BPTT)の出発点です。

flowchart LR
    x1["x_t-1"] --> h1(("h_t-1"))
    x2["x_t"] --> h2(("h_t"))
    x3["x_t+1"] --> h3(("h_t+1"))
    h0(("h_t-2")) -->|"W_hh"| h1
    h1 -->|"W_hh (同じ重み)"| h2
    h2 -->|"W_hh (同じ重み)"| h3
    h1 --> y1["y_t-1"]
    h2 --> y2["y_t"]
    h3 --> y3["y_t+1"]

同じ WhhW_{hh}(同じ重み)が時刻をまたいで何度も登場する点に注目してください。これが次の勾配問題の原因になります。


3. BPTT(通時的誤差逆伝播)

学習は、展開した網に 誤差逆伝播法 をそのまま適用するだけです。これを BPTT(Backpropagation Through Time) と呼びます。

各時刻の損失の合計 L=tLtL = \sum_t L_t について、再帰重み WhhW_{hh} の勾配は次の二重和になります。

LWhh=tk=1tLty^ty^thththk時刻 kt の伝播hkWhh\frac{\partial L}{\partial W_{hh}} = \sum_{t}\sum_{k=1}^{t} \frac{\partial L_t}{\partial \hat y_t}\, \frac{\partial \hat y_t}{\partial h_t}\, \underbrace{\frac{\partial h_t}{\partial h_k}}_{\text{時刻 } k\to t \text{ の伝播}}\, \frac{\partial h_k}{\partial W_{hh}}

ポイントは、時刻 kk から時刻 tt へ勾配を運ぶ部分 hthk\dfrac{\partial h_t}{\partial h_k} が、1ステップぶんのヤコビアンの で書けることです。

hthk=j=k+1thjhj1,hjhj1=diag ⁣(ϕ())Whh\frac{\partial h_t}{\partial h_k} = \prod_{j=k+1}^{t} \frac{\partial h_j}{\partial h_{j-1}}, \qquad \frac{\partial h_j}{\partial h_{j-1}} = \operatorname{diag}\!\big(\phi'(\cdot)\big)\,W_{hh}

要するに:「tkt-k ステップ離れた過去まで勾配を届ける」には、diag(ϕ)Whh\operatorname{diag}(\phi')W_{hh}tkt-k 回掛ける 必要がある。同じ行列の累乗が現れるのがRNN特有です。


4. 勾配消失/爆発:長期依存が学べない

上のヤコビアン積のノルムを評価すると、なぜ長期依存が学べないかが見えます。1ステップぶんのノルムは

hjhj1γϕγW\left\|\frac{\partial h_j}{\partial h_{j-1}}\right\| \le \gamma_\phi\,\gamma_W

で抑えられます。ここで γW\gamma_WWhhW_{hh} の最大特異値(スペクトルノルム)、γϕ\gamma_\phi は活性化の微分の上界(tanh\tanh なら 11sigmoid\mathrm{sigmoid} なら 0.250.25)。したがって tkt-k ステップでは

hthk(γϕγW)tk\left\|\frac{\partial h_t}{\partial h_k}\right\| \le (\gamma_\phi\,\gamma_W)^{\,t-k}
xychart-beta
    title "ステップ数に対する勾配の大きさ(係数の累乗)"
    x-axis "さかのぼるステップ数" [10, 20, 30, 40, 50]
    y-axis "勾配の相対的な大きさ" 0 --> 2.5
    line [0.35, 0.12, 0.04, 0.015, 0.005]
    line [1.2, 1.5, 1.8, 2.1, 2.4]

下の線(係数 >1>1)は爆発、上から落ちていく線(係数 <1<1)は消失を表します。0.9500.0050.9^{50}\approx 0.0051.1501171.1^{50}\approx 117 のように、わずかな係数差が深刻な差になります。

補足:tanh\tanhsigmoid\mathrm{sigmoid} は入力が大きいと微分がほぼ0(飽和)になり、γϕ\gamma_\phi が小さくなりがちです。これは 活性化関数 で扱った飽和の問題と同根です。

二つの問題への対処は別物


5. LSTM:セル状態という「勾配の高速道路」

LSTM(Long Short-Term Memory) は、隠れ状態とは別に セル状態 ctc_t という記憶専用の経路を持ち、情報の出入りを3つの ゲート で制御します。ゲートはすべて sigmoid\mathrm{sigmoid}[0,1][0,1] を出力する「弁」です(\odot は要素ごとの積)。

ft=σ ⁣(Wxfxt+Whfht1+bf)忘却ゲート(古い記憶をどれだけ残すか)it=σ ⁣(Wxixt+Whiht1+bi)入力ゲート(新情報をどれだけ書き込むか)ot=σ ⁣(Wxoxt+Whoht1+bo)出力ゲート(記憶をどれだけ外に出すか)c~t=tanh ⁣(Wxcxt+Whcht1+bc)書き込み候補\begin{aligned} f_t &= \sigma\!\left(W_{xf}x_t + W_{hf}h_{t-1} + b_f\right) &&\text{忘却ゲート(古い記憶をどれだけ残すか)}\\ i_t &= \sigma\!\left(W_{xi}x_t + W_{hi}h_{t-1} + b_i\right) &&\text{入力ゲート(新情報をどれだけ書き込むか)}\\ o_t &= \sigma\!\left(W_{xo}x_t + W_{ho}h_{t-1} + b_o\right) &&\text{出力ゲート(記憶をどれだけ外に出すか)}\\ \tilde c_t &= \tanh\!\left(W_{xc}x_t + W_{hc}h_{t-1} + b_c\right) &&\text{書き込み候補} \end{aligned} ct=ftct1+itc~t(セル状態の更新)c_t = f_t \odot c_{t-1} + i_t \odot \tilde c_t \qquad(\text{セル状態の更新}) ht=ottanh(ct)(隠れ状態=外に見せる記憶)h_t = o_t \odot \tanh(c_t) \qquad(\text{隠れ状態=外に見せる記憶})

要するに:セル状態 ctc_t が長期記憶の本体で、忘却ゲート ftf_t で「前を残す量」、入力ゲート iti_t で「今を足す量」を決め、出力ゲート oto_t で「外に出す量」を決める。

flowchart LR
    cprev(("c_t-1")) -->|"× f_t (忘却)"| add(("+"))
    cand["c~_t (候補)"] -->|"× i_t (入力)"| add
    add --> cnow(("c_t"))
    cnow -->|"tanh して × o_t (出力)"| hnow(("h_t"))
    xh["x_t と h_t-1"] -.->|"ゲート f_t, i_t, o_t を計算"| add

なぜ勾配が流れるのか(加法的更新がカギ)

通常のRNNでは ht1hth_{t-1}\to h_t の伝播が diag(ϕ)Whh\operatorname{diag}(\phi')W_{hh} という 乗法的 なものでした。LSTMのセル状態を見ると、更新が 足し算 なので、セル状態どうしのヤコビアンは

ctct1=diag(ft)\frac{\partial c_t}{\partial c_{t-1}} = \operatorname{diag}(f_t)

になります(他の項は ct1c_{t-1} を直接含まないため、この経路が主役)。したがって kk から tt への勾配は

ctckj=k+1tdiag(fj)\frac{\partial c_t}{\partial c_k} \approx \prod_{j=k+1}^{t} \operatorname{diag}(f_j)

で、重み行列 WW も活性化の微分も掛からず、忘却ゲート fjf_j だけが掛かりますfj1f_j\approx 1(=記憶を保つ)に学習されれば、積はほぼ1のまま遠くまで伝わります。これが「定数誤差カルーセル(constant error carousel)」=自己ループの重みが実質1の経路で、いわば 勾配の高速道路 です。

直観:忘却ゲートを開けっ放し(f1f\approx1)・入力ゲートを閉じる(i0i\approx0)なら、セル状態は何ステップでもほぼ不変で運ばれる。だから長期依存が学べる。


6. GRU:ゲートを2つに簡略化

GRU(Gated Recurrent Unit) は、LSTMから セル状態と出力ゲートを廃し、ゲートを2つ に減らした軽量版です。隠れ状態 hth_t 一本で長期記憶も担います。

zt=σ ⁣(Wxzxt+Whzht1+bz)更新ゲート(前をどれだけ残すか)rt=σ ⁣(Wxrxt+Whrht1+br)リセットゲート(前をどれだけ参照するか)h~t=tanh ⁣(Wxhxt+Whh(rtht1)+bh)書き込み候補\begin{aligned} z_t &= \sigma\!\left(W_{xz}x_t + W_{hz}h_{t-1} + b_z\right) &&\text{更新ゲート(前をどれだけ残すか)}\\ r_t &= \sigma\!\left(W_{xr}x_t + W_{hr}h_{t-1} + b_r\right) &&\text{リセットゲート(前をどれだけ参照するか)}\\ \tilde h_t &= \tanh\!\left(W_{xh}x_t + W_{hh}(r_t \odot h_{t-1}) + b_h\right) &&\text{書き込み候補} \end{aligned} ht=(1zt)ht1+zth~t(隠れ状態の更新)h_t = (1 - z_t)\odot h_{t-1} + z_t \odot \tilde h_t \qquad(\text{隠れ状態の更新})

要するに:更新ゲート ztz_t が「前の記憶 ht1h_{t-1}」と「新候補 h~t\tilde h_t」の 配分 を1つの弁で決める(zt0z_t\approx0 なら前をそのまま保つ=長期記憶)。リセットゲート rtr_t は候補を作るとき過去をどれだけ見るか。

LSTMとの違い

LSTMGRU
記憶の経路隠れ状態 hth_tセル状態 ctc_t(別経路)隠れ状態 hth_t のみ
ゲート数3(忘却・入力・出力)2(更新・リセット)
パラメータ多い少ない(約3/4)。学習が速い傾向
性能データ・タスク次第多くのタスクで同等。明確な優劣はタスク依存

GRUは「1zt1-z_tztz_t」で残す/書き込むを連動させており、LSTMの忘却・入力ゲートを1つにまとめたものと見なせます。勾配が流れる原理(残す配分を1に近づけられる加法的経路)はLSTMと同じです。


7. 発展:双方向・多層・そしてTransformerへ


⚠️ よくある誤解・落とし穴


対応するシミュレーション

simulations/rnn_gradient.py:単純な線形RNNで、時間をさかのぼる勾配が再帰重みの累乗 wTw^T になることを示します。w<1w<1 なら勾配が指数的に消え(勾配消失)、w>1w>1 なら爆発することを系列長に対する対数軸で可視化します。tanh の微分が1以下なので実際は消失が起きやすいこと、LSTM/GRU がセル状態の加法的経路で勾配の“高速道路”を作りこれを緩和することにも触れます。

BPTTの勾配消失・爆発(重みの累乗)

関連ノート