概要
RNN(Recurrent Neural Network)は、系列データを処理するために設計されたニューラルネットワークである。通常の順伝播ネットワークが固定長の入力しか扱えないのに対し、RNNは隠れ状態を次のタイムステップへ引き継ぐことで、任意長の系列を処理できる。テキスト・音声・時系列など「順序に意味がある」データに自然に適用できる。
直感・モチベーション
「彼女は映画を見た。とても______だった。」という文章の空欄を予測するとき、「彼女は映画を見た」という過去の文脈が必要になる。通常の全結合ネットワークは各入力を独立に処理するため、この「記憶」を持てない。
RNNの設計思想は「隠れ状態(hidden state)を記憶として引き継ぐ」ことである。各ステップで新しい入力と前ステップの記憶を合わせて新しい記憶を作り、それを出力と次のステップへ渡す。
トレードオフ:
- 利点: 任意長の系列を一定サイズのネットワークで処理できる。パラメータが系列長に依存しない
- 限界: 長い系列では過去の情報が薄れる(勾配消失問題)。並列化が困難で学習が遅い
数学的定式化
時刻 の入力 、隠れ状態 、出力 に対して:
パラメータは であり、全タイムステップで共有される(重み共有)。
導出を見る
BPTT(Backpropagation Through Time):
損失 に対して、 の勾配は:
は に依存するため、連鎖律を展開すると:
ここで (tanhの微分)。
この積が長い系列で問題を起こす:
- が続くと積が指数的に0に近づく → 勾配消失
- が続くと積が指数的に発散 → 勾配爆発(勾配クリッピングで対処)
重要な性質・注意点
- 勾配消失: の飽和域では勾配がほぼ0になり、100ステップ以上遡ることが実質的に困難。LSTMやGRUで解決
- 勾配爆発: 勾配のノルムが閾値を超えたらスケーリングする「勾配クリッピング」が標準的な対処法
- 重み共有: 同じ を全ステップで使うため、系列長に関係なくパラメータ数が一定
まとめ
- 隠れ状態を次ステップへ引き継ぐことで系列の「記憶」を実現
- パラメータは全タイムステップで共有され、BPTTで学習する
- 長距離依存の学習が苦手(勾配消失)→ LSTMやGRUが実用上の解決策
- Transformerの登場以降は長距離依存タスクでは使われにくくなったが、軽量なオンライン処理では今も有用
実装例
pythonimport math def tanh(x: float) -> float: return math.tanh(x) def tanh_deriv(y: float) -> float: return 1.0 - y ** 2 class RNN: def __init__(self, input_dim: int, hidden_dim: int, output_dim: int): import random scale = 0.01 self.Wxh = [[random.gauss(0, scale) for _ in range(input_dim)] for _ in range(hidden_dim)] self.Whh = [[random.gauss(0, scale) for _ in range(hidden_dim)] for _ in range(hidden_dim)] self.Why = [[random.gauss(0, scale) for _ in range(hidden_dim)] for _ in range(output_dim)] self.bh = [0.0] * hidden_dim self.by = [0.0] * output_dim self.hidden_dim = hidden_dim def matvec(self, W: list[list[float]], x: list[float]) -> list[float]: return [sum(W[i][j] * x[j] for j in range(len(x))) for i in range(len(W))] def forward(self, xs: list[list[float]]) -> tuple: h = [0.0] * self.hidden_dim hs, ys = [], [] for x in xs: raw = [self.matvec(self.Wxh, x)[i] + self.matvec(self.Whh, h)[i] + self.bh[i] for i in range(self.hidden_dim)] h = [tanh(r) for r in raw] y = [sum(self.Why[i][j] * h[j] for j in range(self.hidden_dim)) + self.by[i] for i in range(len(self.by))] hs.append(h[:]) ys.append(y) return hs, ys # 使用例 model = RNN(input_dim=4, hidden_dim=8, output_dim=2) xs = [[0.1, 0.2, 0.3, 0.4]] * 5 # 長さ5の系列 hs, ys = model.forward(xs) print("最終隠れ状態:", hs[-1][:3], "...")
関連記事
前提知識
- ニューラルネットワーク基礎 — 順伝播・誤差逆伝播の基礎
- 分散仮説 — テキスト系列を扱う動機
派生技術
- LSTM — 勾配消失を解決するゲート機構付きRNN
- GRU — LSTMを簡略化した効率的なゲート付きRNN
- Seq2Seq — RNNを用いたエンコーダ・デコーダ構造
応用事例
- 言語モデル — 次の単語を予測する系列モデル
- 品詞タグ付け — 各トークンにラベルを付ける系列ラベリング
用語解説
| 用語 | 説明 |
|---|---|
| 隠れ状態 | RNNが各タイムステップで保持する「記憶」ベクトル |
| 重み共有 | 全タイムステップで同じパラメータ行列を使う仕組み |
| BPTT | 時間方向に展開した計算グラフを逆伝播する学習アルゴリズム |
| 勾配消失 | 長い系列で逆伝播の勾配が指数的に0に近づく問題 |
| 勾配爆発 | 逆伝播の勾配が指数的に発散する問題 |
| 勾配クリッピング | 勾配のノルムが閾値を超えたときにスケーリングする手法 |