【深層学習】なぜRNNではダメなのか?LSTMの仕組みを解説
こんにちは。今回は、時系列データ処理のデファクトスタンダードとして長年君臨してきたLSTM (Long Short-Term Memory) について解説します。
「RNNと何が違うの?」「数式が多くて挫折した」という方に向けて、**「情報のベルトコンベア」**という比喩を使いながら、その内部構造を紐解いていきます。
HTMLを使って見やすく整えたのがこちらです。
人間の思考とRNN
記事を読み始める前に、少し自分の思考プロセスを振り返ってみてください。
私たちは毎秒、ゼロから思考を開始しているわけではありません。この記事の文章を読むときも、前の単語の理解に基づいて、今の単語を理解しています。すべてを捨てて、またゼロから考え始めるわけではありません。
つまり、人間の思考には「持続性」があるといえます。
従来のニューラルネットワーク(Feed-Forward NN)には、この「持続性」がありませんでした。その欠点を克服するために生まれたのが リカレントニューラルネットワーク(RNN) です。
RNNのループ構造
RNNは、自分自身へのループ機能を持つことで情報の永続化を可能にしました。
graph LR
X[入力 x] --> A[A]
A -->|ループ| A
A --> H[出力 h]
これは、「各ステップで一つ前の情報を受け取った上で処理を実行する」ことを意味します。これにより、RNNは前のシーケンスの意味を理解しながら、文脈を持った学習が可能になります。
RNNが抱える「長期依存」の問題
理論上、RNNは過去のあらゆる情報を保持できるはずです。しかし、現実には大きな壁にぶつかりました。それが 長期依存(Long-Term Dependency)の問題 です。
必要な情報が「遠すぎる」
例えば、「空の色は青い」という文脈を予測する場合、直前の情報があれば十分です。しかし、長い文章の最後で、冒頭に出てきた主語を特定する必要がある場合はどうでしょうか?
短期的な依存: 最新の情報と現在の情報のみで解決可能。RNNは得意。
長期的な依存: ずっと過去の重要な情報が必要。RNNはこれを学習するのが非常に困難。
理論的にはパラメータを完璧に調整すれば可能ですが、現実的な学習(勾配降下法など)では、誤差逆伝播の過程で勾配が消失または爆発してしまい、過去の情報をうまく参照できなくなってしまうのです。
救世主:LSTMネットワーク
この「長期依存の問題」を解決するために設計されたのが、LSTM (Long Short-Term Memory) です。
LSTMは、標準的なRNNよりもはるかに優れた性能を発揮します。その成功の鍵は、「長期的な情報を保持すること」をデフォルトの動作として組み込んでいる点にあります。
核となる概念:セルの状態(Cell State)
LSTMの最大の特徴は、図の上部を貫く水平線、**セルの状態(Cell State)**です。
これはよく**「ベルトコンベア」**に例えられます。
情報は全体を一直線に流れる。
わずかな線形相互作用(足し算や掛け算)のみが存在する。
情報は変更されずにそのまま流れていくことが容易。
この「ベルトコンベア」があるおかげで、LSTMは遥か過去の情報を、未来のステップまで劣化させずに運ぶことができるのです。
LSTMの「3つのゲート」
もちろん、ただ流すだけでは意味がありません。LSTMには、このベルトコンベア(セルの状態)に対して、情報を「追加」したり「削除」したりするための ゲート(Gate) という仕組みがあります。
ゲートは シグモイド関数(0~1を出力) と 点ごとの乗算 で構成されています。
0: 「何も通さない」=「忘れる」
1: 「すべて通す」=「覚えている」
LSTMは以下の3つのステップで情報を制御します。
Step 1: 忘却ゲート(Forget Gate)
最初のステップは、「過去の情報の何を捨てるか」 を決めることです。
前の隠れ層の状態
Step 2: 入力ゲート(Input Gate)
次に、「新しい情報の何をセル状態に保存するか」 を決めます。これは2つの部分に分かれます。
更新の決定: シグモイド層(入力ゲート)が、どの値を更新するかを決める。
候補の作成: tanh層が、セルに加えられる新たな候補値のベクトル
ここでセル状態が更新されます。
「古い記憶をどれくらい残すか(忘却)」と「新しい記憶をどれくらい足すか(入力)」を計算し、ベルトコンベア上の情報を書き換えます。
Step 3: 出力ゲート(Output Gate)
最後に、「今のセル状態に基づいて何を出力するか」 を決めます。
セル状態がそのまま出力されるわけではなく、フィルタリングされたものが出力(隠れ状態
シグモイド層で、セル状態のどの部分を出力するかを決める(
セル状態
両者を掛け合わせる。
実装イメージ(NumPy)
数式だけではイメージしづらい方のために、NumPyによる簡易的な実装イメージを載せておきます。
LSTMの1ステップ分の処理イメージ
def lstm_step(x_t, h_prev, C_prev, W, b):
# 入力と前の隠れ状態を結合
combined = np.vstack((h_prev, x_t))
# 1. 忘却ゲート: 過去をどれくらい忘れるか
f_t = sigmoid(np.dot(W['f'], combined) + b['f'])
# 2. 入力ゲート: 新しい情報をどれくらい足すか
i_t = sigmoid(np.dot(W['i'], combined) + b['i'])
C_tilde = np.tanh(np.dot(W['c'], combined) + b['c'])
# ★ セル状態の更新 (ベルトコンベア)
C_t = f_t * C_prev + i_t * C_tilde
# 3. 出力ゲート: 次の隠れ状態として何を出すか
o_t = sigmoid(np.dot(W['o'], combined) + b['o'])
h_t = o_t * np.tanh(C_t)
return h_t, C_t
Discussion