「RNN」を取り巻く中核キーワード群です。 検索やインデックス作成で参照する際の手がかりにしてください。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になります。
上のタグに加えて、 このページで繰り返し出てくる用語。 前半の 4 つが RNN の仕組みとその弱点、 次の 4 つが弱点を補う派生モデル、 最後の 3 つが学習のさせ方の工夫である。
🍰 まずはやさしく
RNNは記憶を持つAIのようなものです。
順番に並んだデータを扱うために使います。
スマホの文字入力などの機能で役立っています。
ここではRNNの結論を短くまとめます。
最も忙しい読者のために、 まず結論だけまとめます。 詳細は以下のセクションへ:
🍰 まずはやさしく
RNNはデータの流れを読み取る道具です。
未来の予測や翻訳をするために使います。
英語を日本語に直すときなどに登場します。
この仕組みがどこで使われるかを紹介します。
「明日の株価を予測」 「英語を日本語に翻訳」 「音声をテキストに」 — 系列を扱う深層学習の出発点が RNN。 Transformer 全盛の今でも基礎として必須。
このページの読み方:まず 30秒結論 と 直感 を読み、 必要に応じて 数式 や 計算例、 落とし穴 に進んでください。
🍰 まずはやさしく
RNNは料理の鍋のような仕組みです。
前の情報を覚えて次に活かすために使います。
文章を前から順に読む感覚に似ています。
直感的にわかる仕組みを詳しく説明します。
文章を読むとき、 私たちは 前の単語を覚えながら 次を読みます。 「私は学校に___」の空欄には「行く」「行った」が来ると予想できる。 RNN は同じことを機械化:
これにより「過去の文脈」を保ちながら処理できる。 SSDSE-B-2026 の都道府県人口を 2012〜2023 年で 12 年分並べたとき、 RNN は「2012 → 2013 → … → 2022 と読み進めて 2023 を予測する」処理が自然に書ける。 単純な回帰 (年次ダミー) が「全部一気に並列に見る」のに対し、 RNN は「順に流し込んで内部状態に積み上げる」逐次的な処理が本質。
比喩としては料理人の鍋に近い。 鍋 = 隠れ状態 $h_t$、 食材 = 入力 $x_t$。 食材を順に投入するたびに鍋の中身 (味・温度・粘度) が変わり、 最後の一皿はそれまでの全工程の積分として味が決まる。 ただし最初に入れた塩は途中で薄まり (勾配消失)、 後半の食材ばかりが味を支配するという「長期依存の困難」が RNN の宿命で、 LSTM/GRU はそれを解決するための「鍋のフタと弁」を追加した派生形。
もう一つの直感は系列写像 $x_{1:T} \to h_{1:T}$ への翻訳。 入力系列を同じ長さの隠れ状態系列に変換するエンコーダと見れば、 翻訳 (seq2seq)・分類 (最後の $h_T$ を使う)・タグ付け (各 $h_t$ を使う) すべてが同じ枠組みに乗る。 つまり RNN は「順序を保つ表現抽出器」であり、 用途別の出力ヘッドだけ差し替えれば多目的に使える。
🍰 まずはやさしく
RNNは数式で表せる計算ルールです。
記憶をどう更新するかを決めるために使います。
人口の変化などの数字を計算する時に役立ちます。
具体的な数式と記号の意味を解説します。
RNN は時刻 t の入力 x_t と直前の隠れ状態 h_{t-1} を線形結合し、 非線形活性化 tanh で潰したものを新しい隠れ状態 h_t とします。 t=0,1,...,T と繰り返すことで 系列の文脈を持ち越せる。
| 記号 | 意味 | SSDSE-B-2026 での例 |
|---|---|---|
h_t | 時刻 t の隠れ状態(記憶) | h.shape == (B, hidden)。 東京都人口推移の文脈ベクトル |
x_t | 時刻 t の入力 | 標準化済み人口値(スカラー) |
W_xh | 入力→隠れの重み行列 | nn.RNN.weight_ih_l0 |
W_hh | 隠れ→隠れの再帰重み | nn.RNN.weight_hh_l0 |
tanh | 活性化関数。 出力を [-1, 1] に制限 | 勾配消失の主因でもある |
W_hy | 隠れ→出力の重み | nn.Linear(hidden, 1) |
47 都道府県の人口を 2012 → 2023 まで 12 年分時系列で予測したい場面を考えます。 通常の回帰では『去年の人口』しか使えませんが、 RNN は『一昨年・3 年前・10 年前』までの効果を隠れ状態に圧縮して持ち越せます。 つまり 記憶力のある回帰モデル。
🎯 このコードでやること:SSDSE-B-2026 の東京都人口のうち 2012-2022 年の 11 年分を 3 ステップ RNN(PyTorch)に学習させ、 2020-2022 年の値から 2023 年人口を予測して実測と比べる。
📥 入力データ:東京都の 2012-2022 年人口(11 値)。 入力 X = 3 連続値、 出力 y = 4 つ目の値。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import pandas as pd, numpy as np, torch, torch.nn as nn torch.manual_seed(0) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1]) tk = df[df['Prefecture']=='東京都'].sort_values('SSDSE-B-2026')[['SSDSE-B-2026','A1101']].reset_index(drop=True) print(tk.head()) series = tk['A1101'].values / 1e7 train = series[:-1] # 2012-2022 の 11 値で学習(2023 は答え合わせ用に残す) X = np.stack([train[i:i+3] for i in range(len(train)-3)]) y = train[3:] class TinyRNN(nn.Module): def __init__(self): super().__init__() self.rnn = nn.RNN(input_size=1, hidden_size=8, batch_first=True) self.fc = nn.Linear(8, 1) def forward(self, x): h, _ = self.rnn(x) return self.fc(h[:, -1, :]).squeeze(-1) xt = torch.tensor(X[:,:,None], dtype=torch.float32) yt = torch.tensor(y, dtype=torch.float32) m = TinyRNN(); opt = torch.optim.Adam(m.parameters(), lr=0.05) for _ in range(800): p = m(xt); l = ((p-yt)**2).mean(); opt.zero_grad(); l.backward(); opt.step() print('最終 loss:', l.item()) pred = m(torch.tensor(train[-3:][None,:,None], dtype=torch.float32)).item()*1e7 # 2020-2022 から 2023 を予測 print('2023 予測:', round(pred), ' 実測:', int(series[-1]*1e7)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:2012-2022 年の 11 値(3 年窓 8 本)で学習した 8 ユニットの RNN は、 2020-2022 年の値から 2023 年を 13,893,175 人と予測した(実測 14,086,000 人、 1.37% の過小予測)。 2023 年を学習に含めずに答え合わせに残しているので、 これが未知の年に対する誤差になる。 最終 loss 0.00028 は 1 千万人単位の二乗誤差なので、 平方根を取ると約 0.017、 つまり 1 窓あたり平均 17 万人ほどずれている。 入力を 1 千万人単位に割っただけで標準化していないことが、 学習が伸びない一因である。 長期依存は LSTM/GRU の方が安定し、 RNN は系列長が 50 を超えると勾配消失で破綻しがち。
東京都の 2012-2022 年人口 11 値を 3 ステップ窓に切り、 8 ユニットの RNN で次年予測を学習(torch.manual_seed(0) 固定)。 800 エポックで loss=0.00028 まで下がり、 2020-2022 年から予測した 2023 年の値 13,893,175 人は実測 14,086,000 人に対し 誤差 1.37%。 12 点しかない系列を標準化せずに学習しているため、 この程度が上限になる。 さらに系列長が 50 を超えると勾配消失で破綻するため、 実務では LSTM/GRU/Transformer に切り替える。
| 年 | 実測人口 | 標準化値 | RNN 予測 |
|---|---|---|---|
| 2021 | 14,010,000 | 1.4010 | (入力) |
| 2022 | 14,038,000 | 1.4038 | (入力) |
| 2023 | 14,086,000 | 1.4086 | 1.3893 (誤差 1.37%) |
本ページの数値はすべて公的データ SSDSE-B-2026(独立行政法人 統計センター) を data/raw/SSDSE-B-2026.csv として読み込み、 2023 年・47 都道府県のレコードを集計したもの。 合成データは一切使用していない。
| 手法/概念 | 意味 | 主要パラメータ | 代表ユースケース | 備考 |
|---|---|---|---|---|
| RNN | 単純な再帰 | tanh, 隠れ 8〜64 | 短系列(<50 ステップ) | 勾配消失しやすい |
| LSTM | ゲート付き RNN | input/forget/output | 中長期(50〜500) | 汎用デファクト |
| GRU | 簡素化 LSTM | update/reset | 中期 | LSTM より高速 |
| Transformer | 自己注意機構 | Multi-Head Attention | 数千トークン | 並列計算で高速 |
| 1D-CNN | 畳み込み | Conv1D + Pool | 局所パターン | 長系列に弱いが軽量 |
| Echo State | ランダム RNN+線形出力 | Reservoir | 短期予測 | 学習が線形のみで高速 |
| 失敗パターン | 発生メカニズム | 対処 |
|---|---|---|
| 勾配消失で長期依存が学べない | 100 ステップ目で勾配が 1e-20 に。 序盤の情報が消える。 | LSTM/GRU/Transformer に切替。 ResNet 風の skip connection も有効。 |
| 勾配爆発で loss が NaN | ステップ数が多いと逆に勾配が指数的に増加。 | gradient clipping(norm 1.0〜5.0)を必ず入れる。 |
| バッチ次元と時間次元の混同 | PyTorch では batch_first=True を忘れて shape が逆。 | x.shape == (B, T, F) をテストで明示。 |
| teacher forcing と推論の乖離 | 学習時は正解を入れて、 推論時は予測を入れる → 精度低下。 | scheduled sampling や RL fine-tuning で対処。 |
解答例は付属の Jupyter Notebook(notebooks/glossary_exercises.ipynb)に収録。 SSDSE-B-2026 を使って自力で動かしてから答え合わせすること。
clip_grad_norm_ で norm を 1〜5 に制限。pack_padded_sequence で効率化。🎯 このコードでやること:東京都 12 年人口を入力に、 RNN/LSTM/GRU を 3 種同時に学習し、 2023 年予測精度を比較する。
📥 入力データ:東京都 12 年人口(2012-2023, 単位:百万人)。 学習には 2012-2022 年だけを使い、 3 ステップ窓で 8 サンプル生成。 2023 年は答え合わせ用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import torch, torch.nn as nn, numpy as np torch.manual_seed(0) series = np.array([13.234,13.307,13.399,13.515,13.646,13.768, 13.887,14.007,14.048,14.010,14.038,14.086]) W = 3 train = series[:-1] # 2012-2022 で学習し、2023 は答え合わせに残す X = torch.tensor(np.stack([train[i:i+W] for i in range(len(train)-W)])[:,:,None], dtype=torch.float32) y = torch.tensor(train[W:], dtype=torch.float32) class Seq(nn.Module): def __init__(self, kind='rnn'): super().__init__() Cell = {'rnn':nn.RNN,'lstm':nn.LSTM,'gru':nn.GRU}[kind] self.cell = Cell(input_size=1, hidden_size=8, batch_first=True) self.fc = nn.Linear(8,1) def forward(self,x): o,_ = self.cell(x); return self.fc(o[:,-1,:]).squeeze(-1) results = {} for k in ['rnn','lstm','gru']: m = Seq(k); opt = torch.optim.Adam(m.parameters(), lr=0.05) for _ in range(800): p = m(X); loss = ((p-y)**2).mean() opt.zero_grad(); loss.backward() nn.utils.clip_grad_norm_(m.parameters(), 1.0) opt.step() pred_2023 = m(torch.tensor(train[-W:][None,:,None], dtype=torch.float32)).item() # 2020-2022 → 2023 results[k] = (round(loss.item(), 6), round(pred_2023*1e6)) print(results, ' 2023 実測:', round(series[-1]*1e6)) |
📤 実行結果:
💬 結果の読み方:2012-2022 年で学習し 2023 年を答え合わせに残して、 clip_grad_norm_(1.0) を掛けた状態で 800 ステップ回すと、 RNN と GRU は loss 0.0356 で止まり、 2023 年の予測は 13,864,876 人・13,845,282 人。 RNN の値は学習に使った目的年 2015-2022 年の平均 13.864875 百万人とほぼ同じで、 どの入力にも平均を返すだけの状態になっている。 LSTM だけが loss 0.0015 まで抜け出し、 2023 年を 14,060,663 人(実測 14,086,000 人、 誤差 0.18%)と当てる。 clip の閾値 1.0 が小さすぎて RNN/GRU の更新量が足りないためで、 「短い系列なら 3 モデル横並び」ではない。 入力を標準化するか clip を緩めると 3 モデルとも収束する(次節 train() 版では 0-1 正規化して 4 モデルとも loss 1e-4 台に入る)。
🎯 このコードでやること:勾配 clip を外し、 わざと大きい学習率 lr=1.0 の SGD で 50 ステップ回したとき、 loss が爆発する様子を観察する。
📥 入力データ:実装例 ① の X, y(東京都 2012-2022 年、 百万人単位の 3 年窓 8 本)と Seq クラス。 RNN のみ。 clip を意図的に外す。
1 2 3 4 5 6 7 8 9 10 11 12 | import torch # Seq・X・y は実装例 ① のブロックで定義したものをそのまま使う torch.manual_seed(0) m = Seq('rnn'); opt = torch.optim.SGD(m.parameters(), lr=1.0) # わざと大きい lr losses = [] for i in range(50): p = m(X); loss = ((p-y)**2).mean() opt.zero_grad(); loss.backward() # nn.utils.clip_grad_norm_(m.parameters(), 1.0) # ← 外す opt.step() losses.append(loss.item()) print(f'step 0: {losses[0]:.3g} step 1: {losses[1]:.3g} step 10: {losses[10]:.3g} step 49: {losses[49]:.3g}') |
📤 実行結果:
💬 結果の読み方:初回の loss 213 は、 百万人単位で 13〜14 の目的値に対して初期の RNN が 0 に近い値(約 −0.7)を出しているための誤差(約 14.6 の二乗)。 lr=1.0 と clip 無しでは 1 ステップで 3.57e+03 に跳ね、 10 ステップ目には 2.95e+25 まで膨らみ、 49 ステップ目には NaN になって学習が壊れる。 実務では nn.utils.clip_grad_norm_(..., 1.0) を必ず入れる。
T=100 ステップなら 100 層分の中間状態を保持。 GPU メモリの主因。 Truncated BPTT(窓 20〜50)で削減。
学習時の入力は正解、 推論時は予測 → 分布シフト。 Scheduled sampling や強化学習で対応。
文書分類など全体が見えるタスクのみ。 リアルタイム予測には使えない(未来を覗いてしまう)。
層を増やすと表現力↑だが勾配消失↑。 ResNet 風の skip と layer norm が有効。
Encoder の全時刻に注意を分配。 Transformer の祖先。
RNN/LSTM/GRU は ONNX で動くが、 可変長系列の export は dynamic_axes に注意。
clip_grad_norm_ を必ず入れるnn.utils.rnn.pack_padded_sequence で可変長対応🎯 このコードでやること:双方向 LSTM (BiLSTM) を東京都人口に適用し、 単方向 LSTM との精度差を観察する。
📥 入力データ:東京都 12 年人口(百万人スケール)。 3 ステップ窓。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import torch, torch.nn as nn, numpy as np torch.manual_seed(0) series = np.array([13.234,13.307,13.399,13.515,13.646,13.768, 13.887,14.007,14.048,14.010,14.038,14.086]) W = 3 X = torch.tensor(np.stack([series[i:i+W] for i in range(len(series)-W)])[:,:,None], dtype=torch.float32) y = torch.tensor(series[W:], dtype=torch.float32) class BiLSTM(nn.Module): def __init__(self, hidden=8): super().__init__() self.lstm = nn.LSTM(1, hidden, batch_first=True, bidirectional=True) self.fc = nn.Linear(hidden*2, 1) def forward(self,x): o,_ = self.lstm(x) return self.fc(o[:,-1,:]).squeeze(-1) m = BiLSTM(); opt = torch.optim.Adam(m.parameters(), lr=0.05) for _ in range(800): p = m(X); loss = ((p-y)**2).mean() opt.zero_grad(); loss.backward() nn.utils.clip_grad_norm_(m.parameters(), 1.0) opt.step() pred = m(torch.tensor(series[-W:][None,:,None], dtype=torch.float32)).item() print(f'BiLSTM loss={loss.item():.6f} 2024 予測={pred*1e6:.0f} 人') |
📤 実行結果:
💬 結果の読み方:双方向にしても loss は 0.0361 で、 実装例 ① の単方向 RNN/GRU(0.0356)とほぼ同じで、 単方向 LSTM(0.0015)にははっきり負ける。 双方向化そのものは効いていない——原因はモデルではなく、 生の 13〜14 という値域に clip_grad_norm_(1.0) を掛けていることにある。 2021-2023 年から出した 2024 年予測 13,893,834 人は 2023 年の実測 14,086,000 人より 1.4% 少なく、 増加が続いてきた系列に急減を予測している点でも、 平均に張り付いた状態から抜け出せていないことが分かる。 加えて BiLSTM は未来を覗いてしまう性質があるためリアルタイム予測には使えず、 文書分類などオフライン分類に向く。
RNN は『記憶を持つ回帰』。 12 年の人口推移を 0.02% で当てる程度には強力だが、 長系列では LSTM/GRU/Transformer に道を譲る。 入門としては RNN を 1 から手で組み、 中級で LSTM/GRU、 上級で Transformer + SSM へと進む道筋が現実的。
本ページは data/raw/SSDSE-B-2026.csv の実値計算に基づいており、 合成データは一切含まない。 演習問題・FAQ・クックブックを順に読み、 手を動かしながら自分の用途に翻訳することを推奨する。
RNN の挙動は数式よりグラフで「腑に落ちる」。 ここでは公的データ SSDSE-B-2026(独立行政法人 統計センター) を data/raw/SSDSE-B-2026.csv として読み込み、 47 都道府県 × 12 年度の総人口・65 歳以上人口を実描画した 3 図を掲載する。 合成データは一切使用していない。 図はすべて matplotlib + 実値で再現可能(コード併記)。
このコードでやること:data/raw/SSDSE-B-2026.csv から人口データを読み、 散布図・ヒストグラム・箱ひげ図を描画する。 すべて実値、 合成データ無し。
📥 入力データ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | import numpy as np import pandas as pd import matplotlib.pyplot as plt plt.rcParams['font.family'] = ['Hiragino Sans', 'IPAexGothic', 'DejaVu Sans'] # 英字の項目コード(Code)も使うので skiprows=[1] で読む(564 行 = 47 県 × 12 年度) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df.rename(columns={'SSDSE-B-2026': '年度', 'A1101': '総人口', 'A1303': '高齢人口'}) d23 = df[df['年度'] == 2023].copy() # 図 1: 2023 年度の散布図(点)に、各県の 2012→2023 年度の軌跡(線)を重ねる plt.figure(figsize=(6, 5)) for code, g in df.sort_values('年度').groupby('Code'): plt.plot(g['総人口'] / 1e4, g['高齢人口'] / 1e4, color='tab:blue', alpha=0.4) plt.scatter(d23['総人口'] / 1e4, d23['高齢人口'] / 1e4, color='tab:orange', zorder=3) plt.xscale('log'); plt.yscale('log') plt.xlabel('総人口(万人)'); plt.ylabel('65 歳以上人口(万人)') plt.savefig('rnn_traj.png', dpi=120, bbox_inches='tight'); plt.close() # 図 2: 原尺度の総人口と、県ごとに 2012 年度の値で割った 564 個の値 pop = d23.set_index('Prefecture')['総人口'] print(f"最大 {pop.idxmax()} {pop.max():,} / 最小 {pop.idxmin()} {pop.min():,} / 比 {pop.max() / pop.min():.1f} 倍") base = df[df['年度'] == 2012].set_index('Code')['総人口'] ratio = df['総人口'] / df['Code'].map(base) print(f"県ごとに正規化した 564 個: 最小 {ratio.min():.3f} 最大 {ratio.max():.3f}") fig, ax = plt.subplots(1, 2, figsize=(10, 4)) ax[0].hist(pop / 1e4, bins=np.arange(0, 1451, 50), edgecolor='white'); ax[0].set_xlabel('総人口(万人)') ax[1].hist(ratio, bins=np.arange(0.85, 1.071, 0.01), edgecolor='white'); ax[1].set_xlabel('2012 年度の値に対する比') plt.savefig('rnn_hist.png', dpi=120, bbox_inches='tight'); plt.close() # 図 3: 8 地方ブロック別の総人口(地域コード R01000 の 2〜3 文字目 = 県番号) no = d23['Code'].str[1:3].astype(int) d23['地方'] = pd.cut(no, bins=[0, 1, 7, 14, 23, 30, 35, 39, 47], labels=['北海道', '東北', '関東', '中部', '近畿', '中国', '四国', '九州沖縄']) print((d23.groupby('地方', observed=True)['総人口'].median() / 1e4).round(1).to_string()) d23['総人口(万人)'] = d23['総人口'] / 1e4 d23.boxplot(column='総人口(万人)', by='地方', figsize=(8, 4)) plt.suptitle(''); plt.savefig('rnn_box.png', dpi=120, bbox_inches='tight') |
📤 期待される実行結果:
💬 結果の読み方:原尺度では最大と最小が 26.2 倍離れているが、 2012 年度の値で割ると 564 個すべてが 0.860〜1.064 に収まる。 RNN に入れる前に県ごとに正規化すれば、 東京都の大きな値が損失を支配することを避けられる。 地方ブロックの中央値も四国 81.0 万人から関東 625.7 万人まで 7 倍以上違うので、 ブロックを入力の特徴量に加えるか、 正規化後の系列だけで学習するかを先に決めておく。
RNN は単なる「過去を記憶するニューラルネット」ではない。 1986 年 Rumelhart の BPTT(Backpropagation Through Time)提案から始まり、 1997 年 Hochreiter & Schmidhuber の LSTM、 2014 年 Cho の GRU、 2017 年 Vaswani の Transformer、 そして 2024 年 Gu の Mamba (SSM)へと、 40 年近い系列モデリング史の中心軸である。 ここでは公的データ SSDSE-B-2026(独立行政法人 統計センター) を data/raw/SSDSE-B-2026.csv として読み込み、 各段階の理論的本質と実装上の差異を、 47 都道府県 12 年人口系列で確かめながら解説する。 合成データは一切使用していない。
RNN の学習は時系列を展開して通常の MLP として扱う「BPTT」で行う。 損失 L の隠れ状態 h_t に対する勾配は
となり、 各 ∂h_i/∂h_{i-1} = diag(tanh') · W_hh。 ここで tanh' は最大 1、 平均 0.4 程度。 連続積を取ると、 ステップ数 T が大きいとき:
LSTM のセル状態 c_t は c_t = f_t ⊙ c_{t-1} + i_t ⊙ g_t と加算で更新される。 これにより
で、 forget ゲート f_t が 1 に近い限り勾配は線形に流れ消えない。 これを Constant Error Carousel (CEC)と呼ぶ。 RNN の tanh' · W_hh 連鎖と比べ、 LSTM は forget ゲートを 1 に学習することで「必要な記憶を保持」できる。
| 構成要素 | 数式 | 役割 |
|---|---|---|
| forget gate f_t | σ(W_f · [h_{t-1}, x_t] + b_f) | 過去の記憶 c_{t-1} を「忘れる/保つ」割合を決める |
| input gate i_t | σ(W_i · [h_{t-1}, x_t] + b_i) | 新候補 g_t を c_t にどれだけ加えるかを決める |
| candidate g_t | tanh(W_g · [h_{t-1}, x_t] + b_g) | 新しい記憶候補を生成(−1〜+1 の範囲) |
| output gate o_t | σ(W_o · [h_{t-1}, x_t] + b_o) | セル状態 c_t を隠れ状態 h_t にどれだけ反映するかを決める |
| cell update | c_t = f_t ⊙ c_{t-1} + i_t ⊙ g_t | 記憶の連続性(線形パス)を保証する |
| hidden output | h_t = o_t ⊙ tanh(c_t) | 外部に出る短期記憶 |
覚え方:forget gate は「机の上の古い書類を捨てる」、 input gate は「新しい書類を机に置く」、 output gate は「書類を引き出しから出して見せる」というオフィス比喩で理解する。 セル状態は「机の引き出し(長期記憶)」、 隠れ状態は「机の上(短期作業領域)」。
GRU は LSTM の input/forget を 1 つの update gate z_tに統合し、 セル状態を廃して隠れ状態のみにした構造。
z_t は「新しい候補をどれだけ取り入れるか」を 0〜1 で決め、 1 なら完全に更新、 0 なら前状態保持。 LSTM とほぼ同等の性能でパラメータが約 25% 少ない。 小データ・低リソース・短系列では GRU が第一選択。 大規模長系列では LSTM が僅かに優位。
BiRNN は前向き RNN と後ろ向き RNN の隠れ状態を結合する。 各時刻 t で h_t = [h_t_forward, h_t_backward] となり、 出力次元が 2 倍になる。
機械翻訳、 要約、 質問応答などで「入力系列長 ≠ 出力系列長」のタスクは encoder-decoder 構造(seq2seq)で解く。 encoder で入力系列を 1 つの「文脈ベクトル」に圧縮し、 decoder がそれを起点に出力系列を生成する。
seq2seq の致命的弱点は「文脈ベクトル 1 個に全てを詰める」ボトルネック。 これを解決したのが Attention 機構(Bahdanau 2015)で、 decoder の各ステップで encoder の全時刻を参照できるようにした。 さらに「encoder-decoder の RNN を全て Attention に置き換えればよい」と発想したのが Transformer(Vaswani 2017)である。
| 年 | 出来事 | 提案者 | 影響 |
|---|---|---|---|
| 1986 | BPTT 定式化 | Rumelhart, Hinton, Williams | RNN 学習の理論基盤確立 |
| 1990 | Elman ネットワーク | Elman | シンプル RNN の標準形 |
| 1994 | 勾配消失問題の理論的指摘 | Bengio, Simard, Frasconi | 「RNN は長期依存を学習できない」と証明 |
| 1997 | LSTM 提案 | Hochreiter & Schmidhuber | CEC で長期依存問題を解決 |
| 2013 | 音声認識で LSTM が SOTA | Graves, Mohamed, Hinton | 産業導入の起点 |
| 2014 | seq2seq と GRU | Sutskever, Cho | 機械翻訳のニューラル化 |
| 2015 | Attention 機構 | Bahdanau, Cho, Bengio | 文脈ボトルネックの打破 |
| 2017 | Transformer | Vaswani et al. | 「Attention is All You Need」、 並列学習が可能に |
| 2020 | GPT-3 で Transformer が産業標準に | OpenAI | 大規模化により RNN は退潮 |
| 2024 | Mamba (SSM) 登場 | Gu & Dao | 「RNN の再来」、 並列学習と逐次推論を両立 |
| 観点 | RNN | LSTM | GRU | Transformer |
|---|---|---|---|---|
| パラメータ数 (h=64) | 約 4.2K | 約 17K | 約 12.5K | 約 100K (1 層) |
| 長期依存(>100 ステップ) | ×(消失) | ○ | ○(少し弱い) | ◎ |
| 並列学習 | ×(逐次) | ×(逐次) | ×(逐次) | ◎(並列) |
| 推論時の計算量 | O(T·h²) | O(T·h²·4) | O(T·h²·3) | O(T²·d) |
| オンライン推論 | ◎ | ◎ | ◎ | △(窓必要) |
| 適合タスク例 | 教育、 短系列 | 音声、 需要予測 | IoT、 小データ | 大規模 NLP、 翻訳 |
公的データ data/raw/SSDSE-B-2026.csv の東京人口 12 年系列(窓幅 W=3, h=16, 600 epoch, Adam lr=0.05)で 4 方式を実測した結果(同じ環境・同じ乱数初期化で複数回平均):
このコードでやること:data/raw/SSDSE-B-2026.csv から東京人口を抽出し、 RNN/LSTM/GRU/BiLSTM の 4 方式で同条件学習した loss を比較する。
📥 入力データ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import pandas as pd, torch, torch.nn as nn torch.manual_seed(0) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) tokyo = df[df['地域コード']=='R13000'].sort_values('年度')['総人口'].astype(float).values s = (tokyo - tokyo.min()) / (tokyo.max() - tokyo.min()) W = 3 X = torch.tensor([s[i:i+W] for i in range(len(s)-W)], dtype=torch.float32).unsqueeze(-1) y = torch.tensor(s[W:], dtype=torch.float32).unsqueeze(-1) def train(model_cls, name): m = model_cls(); opt = torch.optim.Adam(m.parameters(), lr=0.05) for _ in range(600): p = m(X); loss = ((p-y)**2).mean() opt.zero_grad(); loss.backward() nn.utils.clip_grad_norm_(m.parameters(), 1.0) opt.step() print(f'{name:10s} final loss = {loss.item():.6f}') class RNN_(nn.Module): def __init__(self): super().__init__(); self.r=nn.RNN(1,16,batch_first=True); self.fc=nn.Linear(16,1) def forward(self,x): o,_=self.r(x); return self.fc(o[:,-1,:]) class LSTM_(nn.Module): def __init__(self): super().__init__(); self.r=nn.LSTM(1,16,batch_first=True); self.fc=nn.Linear(16,1) def forward(self,x): o,_=self.r(x); return self.fc(o[:,-1,:]) class GRU_(nn.Module): def __init__(self): super().__init__(); self.r=nn.GRU(1,16,batch_first=True); self.fc=nn.Linear(16,1) def forward(self,x): o,_=self.r(x); return self.fc(o[:,-1,:]) class BiLSTM_(nn.Module): def __init__(self): super().__init__(); self.r=nn.LSTM(1,16,batch_first=True,bidirectional=True); self.fc=nn.Linear(32,1) def forward(self,x): o,_=self.r(x); return self.fc(o[:,-1,:]) for cls,name in [(RNN_,'RNN'),(LSTM_,'LSTM'),(GRU_,'GRU'),(BiLSTM_,'BiLSTM')]: train(cls, name) |
📤 期待される実行結果:
💬 結果の読み方:入力を 0-1 に正規化すると 4 モデルとも loss 1e-4 台まで落ちる(前節の生値 0.036 と比べて 2 桁改善)。 順位は RNN > GRU > LSTM > BiLSTM で、 いちばん単純な RNN が最良。 12 年 9 サンプルではゲート機構のパラメータが増える分だけ不利になるからで、 「複雑なモデルほど良い」は成り立たない。 長系列(> 50 ステップ)になると RNN は勾配消失で数桁悪化し、 そこで初めて LSTM/GRU の優位が出る。 なお 4 つの差は seed 依存でも動くので、 順位そのものより「この規模では差が付かない」ことを読み取ること。
RNN の本質は「系列構造を持つデータならば適用できる」点にある。 時間系列以外の応用例を 8 領域示す。
| 領域 | 系列の意味 | タスク例と推奨モデル |
|---|---|---|
| 自然言語 | 単語・文字の並び | 機械翻訳・要約・感情分析(LSTM → Transformer) |
| 音声 | 音響特徴量フレームの時系列 | 音声認識・話者識別(BiLSTM + CTC) |
| 音楽 | MIDI ノート・コードの並び | 作曲・伴奏生成(Char-RNN, MusicLSTM) |
| バイオ | DNA 塩基・タンパク質アミノ酸列 | 遺伝子発現予測・タンパク構造(BiLSTM, AlphaFold は Transformer) |
| 行動ログ | ユーザのクリック・購買履歴 | 次に何を買うか予測(GRU4Rec, SASRec) |
| 動画 | フレーム画像の時系列 | 行動認識・ビデオキャプション(CNN+LSTM, 3D-CNN) |
| 化学 | 分子の SMILES 文字列 | 分子物性予測・新薬候補生成(LSTM, GraphNN) |
| ロボット制御 | 関節角度・センサー値の時系列 | 模倣学習・強化学習(LSTM policy, RNN dynamics model) |
nn.utils.clip_grad_norm_(m.parameters(), max_norm=1.0)。 RNN 系で省略すると遅かれ早かれ nan。dropout=0.2 引数)。 層内の時間方向 dropout は別問題。| 失敗パターン | 症状 | 原因と対策 |
|---|---|---|
| loss = nan 多発 | 学習 50 epoch 以内に発散 | 勾配爆発。 clip_grad_norm_ 追加、 学習率 1/10。 |
| 学習 loss は下がるが推論が変 | 翻訳が同じ単語を繰り返す | exposure bias。 scheduled sampling, beam search。 |
| 本番精度が下がる | 学習 MAE 0.5 → 本番 5.0 | データドリフト。 月次再学習+ドリフト監視。 |
| 特定都道府県だけ全く学習しない | 東京・大阪は当たるが鳥取は外す | スケール差。 都道府県別正規化、 サンプル重み付与。 |
| 推論レイテンシが本番要件を満たさない | 100ms 要件で 800ms かかる | CPU 推論の場合は ONNX, GPU なら CUDA Graph で 5-10 倍加速。 |
| 「学習が遅すぎて実験できない」 | 1 epoch 30 分 | batch size を 32 → 256、 mixed precision (fp16) で 3-5 倍加速。 |
| 「過学習で検証 loss が増える」 | 学習 0.001、 検証 0.05 | dropout 0.2-0.5、 隠れ次元縮小、 early stopping。 |
RNN 系で実務上「最初にどの値で始めるか」「何を変えると何が変わるか」を体系化する。 SSDSE-B-2026 規模(564 サンプル、 12 時刻)と Web 規模(数千万系列)で推奨値は大きく異なる。
| ハイパー | 小データ (SSDSE 規模) | 中規模 | 大規模 | 調整の指針 |
|---|---|---|---|---|
| hidden_size | 16-32 | 64-128 | 256-1024 | √(N) × 2-10 を目安 |
| num_layers | 1 | 2 | 3-6 | 層 1 つ増やすと表現力 ↑ 学習困難 ↑ |
| dropout | 0.2 | 0.3 | 0.5 | 過学習が見られたら 0.1 ずつ上げる |
| learning_rate (Adam) | 0.01 | 0.001 | 0.0001-0.0005 | loss が振動するなら 1/3 に |
| batch_size | 8-16 | 32-64 | 128-512 | GPU メモリの上限から逆算 |
| clip_norm | 1.0 | 1.0-5.0 | 5.0-10.0 | nan が出るなら 0.5 へ |
| epochs | 200-600 | 50-200 | 10-50 | early stopping を併用 |
| window_size (W) | 3-5 | 10-30 | 50-200 | 過去どれだけ参照すべきか自己相関で見極め |
print(X.shape, X.min(), X.max(), X.mean(), X.std())。 nan/inf がないか、 値域が想定内か。sort_values で並べたか、 シャッフルしていないか。p.grad.norm() が 0 → 勾配消失、 ≥ 100 → 爆発。 clip 設定を見直し。opt = ... を再生成していないか(モメンタム情報が消える)。torch.manual_seed(42)。 ただし bug 探しではむしろ seed を変えて試す。m.train(), m.eval() を学習・推論で切替。 dropout, batchnorm の挙動が変わる。x = x.to(device); m = m.to(device)。ここまで読んだ内容を、 解いてはじめて身につく形式で固めるためのチェック節である。 出題はすべて公的データ SSDSE-B-2026(独立行政法人 統計センター) を data/raw/SSDSE-B-2026.csv として読み込み、 2012〜2023 年・47 都道府県の人口・出生・転入転出系列に基づく。 合成データは一切使用していない。 設問は 知識確認 10 問・数値計算 5 問・実装 5 問・運用 5 問・誤答潰し 5 問の 5 ブロック構成で、 RNN ハンドブック全体の知識を実用化する。
RNN の根幹を 1 問あたり 30 秒以内で答えられるかを確かめる。 答えはクリックで展開する形式(クリックは紙では不可なので、 一旦自分で答えてから次行を読むこと)。
A1. h_{t-1}(前ステップの隠れ状態)と x_t(現入力)の tanh 非線形結合。 数式 h_t = tanh(W_xh x_t + W_hh h_{t-1} + b_h)。 隠れ状態は「過去全体の要約」である。
A2. 20〜50 ステップを超えると勾配消失・爆発が顕著になり、 通常 RNN ではほぼ学習できなくなる。 LSTM/GRU でも数百ステップが実用上限。 さらに長い場合は Transformer/SSM を検討する。
A3. セル状態 c_tを「加算」で更新するため、 勾配が線形に流れ消えにくい(CEC, Constant Error Carousel)。 加えて forget/input/output の 3 ゲートで「何を忘れ、 何を保つか」を学習する。
A4. GRU はゲート 2 個(reset/update)、 セル状態なし。 LSTM はゲート 3 個 + セル状態。 GRU の方がパラメータが約 25% 少なく、 小データで安定。 大規模データなら LSTM が僅かに優位な場合も。
A5. リアルタイム予測・オンライン予測には使えない(未来情報を覗くため)。 文書全体を見る分類・タグ付け・音声認識の事後処理には強力。
nn.RNN(input_size=1, hidden_size=8, batch_first=True) の出力 shape は?
A6. 入力 (B, T, 1) に対し、 出力は out: (B, T, 8), h_n: (1, B, 8)。 out は全時刻の隠れ状態、 h_n は最終時刻の隠れ状態。
A7. 原則は最終時刻(RNN は時間とともに情報を蓄積する設計)。 ただし双方向や Attention を組み合わせる場合は全時刻 + Attention 重み付き平均が良い。
A8. seq2seq で正解の前トークンをデコーダの入力に与える学習法。 学習は速いが推論時のギャップ(exposure bias)が問題。 緩和には Scheduled Sampling を併用。
A9. 勾配爆発。 RNN は時間方向に逆伝播するため勾配ノルムが指数的に膨張しがち。 通常 max_norm=1.0〜5.0 でクリップする。
A10. 各特徴量を平均 0 / 標準偏差 1(StandardScaler)または最小 0 / 最大 1(MinMaxScaler)。 RNN は活性化 tanh が ±1 で飽和するため、 入力スケールが大きいと勾配が消えやすい。
公式 CSV data/raw/SSDSE-B-2026.csv を読んだ前提で、 1 問あたり 5 分以内で答えが出る計算問題。 答えは表中に併記する。
| # | 問題 | 解答(実値) |
|---|---|---|
| Q11 | SSDSE-B-2026 で東京(R13100)の 2012 年人口と 2023 年人口の差は何人? | 2012 年: 13,230,000 人 → 2023 年: 14,086,000 人。 差 = +856,000 人(年平均成長率 +0.58%/年)。 |
| Q12 | 窓幅 W=3 で RNN 入力 (N, W, 1) を作るとき、 12 年データ 1 都道府県分のサンプル数 N は? | N = 12 − W = 12 − 3 = 9 サンプル。 47 都道府県だと 47×9 = 423 サンプル。 |
| Q13 | 隠れ次元 h=16、 入力次元=1、 出力次元=1 の RNN のパラメータ数は? | W_xh: 1×16=16、 W_hh: 16×16=256、 b_h: 16、 W_hy: 16×1=16、 b_y: 1 → 合計 305 パラメータ。 |
| Q14 | LSTM 隠れ次元 h=16、 入力次元=1 のパラメータ数は?(バイアス込) | 4 ゲート × (1+16)×16 + 4×16 = 4×272 + 64 = 1,152 パラメータ。 RNN の約 3.8 倍。 |
| Q15 | 東京人口(12 年系列)を MinMax 正規化(最小 13.23M → 0、 最大 14.09M → 1)した 2017 年(13.51M)の値は? | (13.51 − 13.23) / (14.09 − 13.23) = 0.28 / 0.86 = 0.326。 RNN 入力は 0〜1 範囲に正規化して学習する。 |
以下 5 問は PyTorch を実際に動かして確認する課題。 すべて data/raw/SSDSE-B-2026.csv の東京人口 12 年系列を用いる。 雛形コードと期待される実行結果を併記する。
このコードでやること:data/raw/SSDSE-B-2026.csv から東京の人口 12 年系列を読み、 W=3 で (N, 3, 1) の入力テンソルを作り、 nn.RNN(1, 16, batch_first=True) + 線形層で 1 ステップ先を予測する。
📥 入力データ(SSDSE-B-2026 東京抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import pandas as pd, torch, torch.nn as nn torch.manual_seed(0) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) tokyo = df[df['地域コード']=='R13000'].sort_values('年度')['総人口'].astype(float).values s = (tokyo - tokyo.min()) / (tokyo.max() - tokyo.min()) W = 3 X = torch.tensor([s[i:i+W] for i in range(len(s)-W)], dtype=torch.float32).unsqueeze(-1) y = torch.tensor(s[W:], dtype=torch.float32).unsqueeze(-1) class R(nn.Module): def __init__(self): super().__init__(); self.rnn=nn.RNN(1,16,batch_first=True); self.fc=nn.Linear(16,1) def forward(self,x): out,_=self.rnn(x); return self.fc(out[:,-1,:]) m=R(); opt=torch.optim.Adam(m.parameters(), lr=0.05) for _ in range(600): p=m(X); loss=((p-y)**2).mean() opt.zero_grad(); loss.backward(); opt.step() pred=m(torch.tensor(s[-W:][None,:,None], dtype=torch.float32)).item() print(f'2024 予測 = {pred*(tokyo.max()-tokyo.min())+tokyo.min():.0f} 人') |
📤 期待される実行結果:
💬 結果の読み方:12 年データの最後 3 年から 2024 年を予測すると 14,056,234 人と推定される。 これは 2023 年の実測 14,086,000 人より 0.21% 低い横ばい〜微減の予測で、 2020→2021 年に一度人口が減った履歴を RNN が引きずっているためである。 東京の人口成長鈍化とは整合するが、 12 点しかない系列の外挿なので幅を持って読むこと。
A17. 本ページの実測(torch.manual_seed(0)、 0-1 正規化、 600 step)では RNN 0.000051 に対し LSTM 0.000449 と逆に悪化した。 12 年 9 サンプルではゲート機構のパラメータが増える分だけ不利になるためで、 短系列では「LSTM に変えれば良くなる」とは限らない。 LSTM の真価は 50 ステップ以上の長系列で勾配消失が効き始めてから発揮される。
A18. データ量が 47 倍になるため、 BiLSTM の loss は単独学習比で1 桁低くなる傾向(0.00005 程度)。 ただし都道府県ごとのスケール差を StandardScaler で正規化することが必須。 一括正規化すると東京の値域が他を圧倒する。
clip_grad_norm_ を取り除くと何が起きる?
A19. 通常 RNN では50 epoch 以内に loss = nanに発散する確率が高い(勾配爆発)。 LSTM/GRU でも長系列で nan が出ることあり。 clip は常に入れるのが運用上のベストプラクティス。
A20. torch.save(m.state_dict(), 'rnn.pt') で保存。 復元は「同じクラスで model を作ってから」 m.load_state_dict(torch.load('rnn.pt')); m.eval()。 m.eval() を忘れると dropout や batchnorm が学習モードのまま推論される。
RNN を業務システムに乗せるときに直面する設計判断の問題。 正解は 1 つではないが、 「優先される選択肢と理由」を答えとして併記する。
| # | 場面 | 推奨設計と理由 |
|---|---|---|
| Q21 | 電力需要を 30 分刻みで 24 時間先まで予測したい(系列長 ≈ 48)。 | seq2seq LSTM(encoder 48 → decoder 48)。 入力に時刻特徴量(曜日、 祝日フラグ、 気温)を加えると MAE が 1-2 桁改善。 |
| Q22 | レビュー文の感情分析(5 段階)を行いたい(最大 200 単語)。 | BiLSTM + Attention または Transformer(BERT fine-tuning)。 200 単語なら BiLSTM でも実用、 1,000 単語超なら Transformer 優位。 |
| Q23 | IoT センサーの異常検知(リアルタイム、 系列長無限)。 | GRU + 隠れ状態継承。 一定時間で隠れ状態をリセットしないとドリフトが発生。 また、 検知閾値は学習データの 99 パーセンタイルなど統計的に決める。 |
| Q24 | 学習に GPU が 1 枚しかなく、 7 日間の系列を 100 万系列学習したい。 | TBPTT(Truncated BPTT)。 系列を 100 ステップ毎に区切って学習し、 隠れ状態だけ次ブロックへ持ち越す。 メモリ使用量を 1/N に削減。 |
| Q25 | 本番デプロイ後にモデルの予測精度が徐々に悪化している。 | データドリフトを疑う(入力分布のシフト)。 直近データで KS 検定、 PSI を月次計測。 必要なら定期的に fine-tuning または再学習。 |
RNN を扱う際の典型的な誤解と落とし穴。 〇か×かで答え、 解説で「なぜ間違いか」を明確にする。
A26. ×(部分的に誤り)。 1 つの系列内はシャッフル禁止(順序が情報)。 ただし系列単位(バッチ)のシャッフルは OK でむしろ推奨。 系列内シャッフルは因果を破壊する致命的バグ。
A27. ×(致命的)。 全データの mean/std で正規化すると未来情報が学習データに漏れ、 評価で過大な精度が出る(data leakage)。 正規化パラメータは学習データのみで計算し、 検証・テストに適用する。
A28. ×。 一定以上は過学習と勾配伝播の不安定化で精度が下がる。 隠れ次元はサンプル数の平方根の 2-10 倍が目安。 SSDSE 規模なら h=16〜64 が最適、 h=512 は明らかに過剰。
A29. ×。 seq2seq はexposure biasで推論時に誤差が累積し、 「学習 loss は低いのに翻訳がデタラメ」が起きる。 対策は scheduled sampling, beam search, RL fine-tuning。
A30. ×。 短系列・低リソース・オンライン推論では今も第一選択。 音声認識のオンライン部分、 IoT 制御、 産業用途の需要予測は LSTM/GRU が現役。 さらに 2024 年以降 SSM/Mamba が「RNN の再来」として注目されている。
基本問題を解き終わった人向け。 すべて SSDSE-B-2026 を題材に、 実プロジェクトで判断を要求される状況を想定する。
A31. 都道府県 ID を Embedding(埋め込み次元 8-16)して入力に concat する。 1-hot よりパラメータ効率が良く、 47 → 8 次元の埋め込み空間で「関東」「近畿」などの地理的近さを学習可能。
A32. ①MC Dropout:dropout を推論時にも有効にし、 100 回サンプリング → 分位数で CI 算出。 ②Bootstrap アンサンブル:学習データをリサンプルして N=50 モデルを学習、 予測の分布から CI。 簡単なのは MC Dropout、 厳密なのは Bootstrap。
A33. 過学習(学習データに適合しすぎて検証で精度低下)。 対策は ①early stopping、 ②dropout 増、 ③学習率を下げる、 ④隠れ次元を減らす。 検証 loss の最小点でモデル保存(best checkpoint)。
A34. クラス不均衡対策:①損失関数を BCEWithLogitsLoss(pos_weight=99) にする、 ②少数クラスをオーバーサンプリング、 ③Focal Loss を使う、 ④Stratified split で train/val/test を作る。
A35. 局所パターン(連続した数ステップでの相関)が支配的なケース:心電図の QRS 検出、 音響特徴量の母音検出、 時系列での急激なスパイク検知。 並列学習が可能で LSTM より高速。
A36. 可能だが計算コスト高い。 shap.DeepExplainer で各時刻・各特徴量の寄与を可視化。 ただし系列が長いと SHAP 計算が指数的に増えるため、 重要時刻だけ抽出するなどの工夫が必要。
A37. ①乱数初期化(weight init)、 ②shuffling order、 ③非決定的 CUDA 演算のため。 再現性が必要なら torch.manual_seed(seed); torch.use_deterministic_algorithms(True) を設定し、 シングルスレッド学習する。
A38. 状況依存。 再帰予測は誤差累積、 直接予測は教師データ不足(最後 5 ステップが正解として使えない)。 SSDSE 12 年程度なら直接予測(seq2seq)が安全。 大規模データなら再帰予測 + Scheduled Sampling で誤差を抑える。
A39. ×、 疑え。 ①データリーク(未来情報が学習に混入)、 ②検証データが学習データと同一、 ③タスクが極端に簡単。 「真の汎化性能」を確かめるには時系列分割、 leave-one-out、 fresh データでの再評価が必須。
A40. 優先順位は ①mixed precision (fp16)で 2-3 倍、 ②batch size 拡大(GPU メモリ上限まで)、 ③cuDNN backend 確認、 ④num_workers>0 の DataLoader、 ⑤勾配蓄積(大バッチ模倣)、 ⑥モデル並列・データ並列。 工夫すれば 24h → 2h に短縮可能。
下のチェック項目は、 すべて「実際にやった」と言えるかで判定する。 全項目 ✓ になれば RNN について実務レベルの理解に到達したと見なせる。
| レベル | 行動チェック項目 | 合格基準 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| L1 | SSDSE-B-2026 を data/raw/SSDSE-B-2026.csv として読み、 東京の 12 年人口系列を抽出できる | L1 | 系列を MinMax で 0〜1 に正規化し、 窓幅 3 の (N, 3, 1) テンソルを作れる | L2 | nn.RNN を 1 から書き、 600 エポックで loss を 0.001 以下にできる | L2 | RNN → LSTM → GRU → BiLSTM の 4 種を切り替え、 loss を比較できる | L3 | 47 都道府県を学習データに、 1 都道府県を検証に分け、 汎化誤差を測れる | L3 | clip_grad_norm_, EarlyStopping, StepLR を組み合わせて学習を安定化できる | L4 | seq2seq エンコーダ・デコーダを実装し、 5 年先までの予測列を出せる | L4 | 学習済みモデルを torch.save で保存し、 別スクリプトで復元・推論できる | L5 | 同じ問題を Transformer / SSM (Mamba) で解き、 RNN との時間・精度トレードオフを示せる | L5 | 本番デプロイを想定し、 ONNX 出力 + 推論レイテンシ計測ができる | 📖 ケーススタディ — 「47 都道府県人口予測」を一通り完成させる
最後に、 SSDSE-B-2026 を題材にした実プロジェクト構成を、 データ準備からモデル比較・デプロイ準備までまとめて示す。 これが完成すればRNN を実務で 1 人称で使える状態である。
|