本ページは 多層パーセプトロン(Multi-Layer Perceptron)を多角的に解説します。 上のチップは、 検索・関連語の手がかりです。
この増補は、MLPを「用語の暗記」ではなく、実データで使い、数式を言葉に戻し、結果の限界まで説明するための補講である。使用データは data/raw/SSDSE-B-2026.csv の実測値であり、np.random による合成データは使わない。
本ページに登場する主要キーワード 10 件。 クリックで該当セクションへ。
🍰 まずはやさしく
脳の仕組みをまねた計算モデルです。
複雑なデータのパターンを見つけるために使います。
スマホのアプリなどでよく使われています。
この章では基本の仕組みと使い方を読みます。
🍰 まずはやさしく
AIを作るための土台となる仕組みです。
データの分析や予測を正しく行うために使います。
地域の気温や支出などのデータを分析します。
この章ではどこでこの仕組みを使うかを読みます。
MLP(多層パーセプトロン、 Multi-Layer Perceptron)は、 深層学習の最も基本的な構造。 Rumelhart, Hinton らによる誤差逆伝播法(1986)で実用化され、 2010 年代の深層学習革命の礎になりました。 CNN や Transformer などの最新モデルも、 内部には MLP を多用しています。
統計・データ解析コンペでMLPが出てくる場面は、探索的分析、特徴量設計、モデル構築、検証、説明資料のどこかに必ずある。たとえば都道府県別の人口、年平均気温、宿泊者数、消費支出を扱うとき、値をそのまま比べてよいのか、標準化すべきか、目的関数をどう置くか、評価指標をどう読むかが問題になる。
| 観点 | 確認する問い | 誤ると起きること |
| 入力 | 列名・単位・年度は何か | 別年度や別単位を混ぜて結論が崩れる |
| 変換 | 標準化・活性化・尺度分類は妥当か | 数値は出るが意味が薄い |
| 評価 | 誤差・順位・係数をどう読むか | 精度だけを見て限界を落とす |
| 報告 | 何を意味しないかを書いたか | 因果や一般化を言い過ぎる |
あなたは MLP (Multi-Layer Perceptron) の用語ページを読んでいる。 これは ニューラルネットワークの最小ビルディングブロック。 CNN・RNN・Transformer も中身は MLP の特化版である(CNN = 重み共有 MLP、 Transformer = 自己注意 + 位置ごと MLP)。 本ページでは 47 都道府県データで「線形回帰 → MLP」と段階的に進めることで、 隠れ層・非線形活性化・バックプロパゲーションが実際に何をしているのかを体験ベースで理解する。
🍰 まずはやさしく
層を重ねたフィルターのようなものです。
直線では表せない複雑な関係を捉えるために使います。
気温と支出のU字のような関係を分析します。
この章では仕組みをイメージで理解して読みます。
MLP の構造:
各層の各ニューロンは 前の層の全ニューロンと結合(fully connected)。 中間層の存在により、 直線では分離できないパターンも学習可能。
普遍近似定理(Cybenko 1989, Hornik 1991):1 つの隠れ層を持つ MLP で、 任意の連続関数を任意の精度で近似できる。 ただし「必要なニューロン数は無限大かも」という条件付き。 実用上は深く(多層に)するほうが効率的(深層学習の理論的根拠)。
MLP の「層を重ねる効果」を体感するには、 SSDSE-B-2026 で 非線形な関係 を 1 つ取り上げるのが早い。 例えば「年平均気温 B4101」と「消費支出 L3221」は、 寒冷地(暖房コスト増)でも温暖地(観光・冷房)でも支出が上がる U 字 に近い。 線形回帰では「気温 1℃ あたり ◯ 円」の 1 本の直線しか引けず、 北海道と沖縄を同時に説明できない。 一方 MLP は 隠れ層が 気温の閾値ごと に異なる係数を発火させ、 「14℃ 未満は寒冷効果」「14〜22℃ は中庸」「22℃ 超は暑熱効果」と 区分的にスロープが切り替わる関数 を作れる。 同じ表の数値でも、 出力までの「途中の道のり」が層数で増えることが MLP の本質。
| 都道府県 | 総人口 A1101 | 年平均気温 B4101 | 消費支出 L3221 | 線形回帰の予測誤差 | 2 層 MLP の予測誤差 |
| 北海道 | 5,092,000 | 11.0 | 296,888 | +18,400 | +2,100 |
| 東京都 | 14,086,000 | 17.6 | 341,320 | −9,500 | +1,300 |
| 沖縄県 | 1,468,000 | 23.8 | 251,222 | +22,800 | −1,600 |
| モデル | 構造 | 強み | 弱み | 典型用途 |
|---|---|---|---|---|
| 線形回帰 | 1 層、 活性化なし | 解析解、 解釈性 | 非線形性なし | ベースライン |
| ロジスティック回帰 | 1 層 + シグモイド | 解析しやすい | 線形分離のみ | 2 値分類 |
| MLP (浅) | 2-3 層 + ReLU | 非線形、 高速 | 表現力に限界 | 表形式データ |
| MLP (深) | 10+ 層 + ReLU + BN | 高表現力 | 勾配問題、 計算量 | 音声、 構造化データ |
| CNN | 畳み込み + プーリング | 並進不変性 | 画像専門 | 画像認識 |
| Transformer | 自己注意 + MLP | 長距離依存 | 計算量 $O(n^2)$ | NLP, LLM |
多層パーセプトロン (MLP) を 3 枚で。 (1) ネットワーク構造 (層・ユニット・全結合)、 (2) 順伝播と活性化、 (3) 誤差逆伝播法による学習サイクル。 これで「入力 → 出力」の流れと「重みがどう更新されるか」が一枚絵で結びつく。
全結合層が 2 段。 隠れ層は ReLU、 出力層は恒等関数 (回帰) または softmax (分類)。 1 つのノードは「重み付き和 → 活性化関数」の 2 段で動く。
線 1 本 = 重み 1 つ。 この図のパラメータは W¹: 3×4=12, b¹: 4, W²: 4×1=4, b²: 1 → 計 21 個。 学習はこの 21 個を更新する作業。
隠れノードは「重み付き和 z = Σ wᵢxᵢ + b」→「活性化 a = ReLU(z) = max(0, z)」の 2 段。 ReLU は入力の正負を切替えるスイッチ。
z<0 なら ReLU で 0 にクリップされる「不活性ノード」になる。 不活性が多いと「死亡 ReLU」問題が起き、 学習が止まる。 Leaky ReLU や ELU が対策。
MLP の学習は 4 ステップの反復。 損失関数 L (MSE / Cross Entropy) → 各重みに対する勾配 ∂L/∂w を chain rule で逆伝播 → SGD/Adam で w ← w − η ∂L/∂w。 これを epoch 回繰り返す。
Adam = SGD + 1 次/2 次モーメント補正。 学習率 η を自動調整するため、 SSDSE 規模では Adam(lr=1e-3) で安定する。
以下 5 問を解いて理解度を確認してください。 回答は本文の該当セクションを参照。
| 層 | 役割 | 活性化(回帰) | 活性化(2 値分類) | 活性化(多クラス) |
|---|---|---|---|---|
| 入力層 | 特徴ベクトルを受け取る | なし(恒等) | なし | なし |
| 隠れ層 (1〜N) | 非線形変換で特徴抽出 | ReLU / GELU | ReLU | ReLU |
| 出力層 | タスク別に予測値を出力 | 恒等(線形) | Sigmoid | Softmax |
→ 出力層の活性化はタスクで決まる。 隠れ層は ReLU が現代の標準。 勾配消失を避けるため Sigmoid/Tanh は隠れ層では避ける。
関連: パーセプトロン / ReLU / シグモイド / ソフトマックス / 誤差逆伝播法 / ニューラルネット / 活性化関数
🍰 まずはやさしく
数式で表した計算の手順です。
コンピュータに正しく計算させるために使います。
テストの点数を予測する式のようなものです。
この章では計算の流れを数式で読みます。
L 層 MLP の順伝播:
誤差逆伝播の核:
$L$ 層 MLP の順伝播:
$$\boldsymbol{h}^{(0)} = \boldsymbol{x}, \quad \boldsymbol{h}^{(l)} = \sigma\left(W^{(l)} \boldsymbol{h}^{(l-1)} + \boldsymbol{b}^{(l)}\right) \;(l = 1, \dots, L-1), \quad \hat{\boldsymbol{y}} = W^{(L)} \boldsymbol{h}^{(L-1)} + \boldsymbol{b}^{(L)}$$
損失(回帰の場合):
$$\mathcal{L} = \frac{1}{N} \sum_{i=1}^{N} \|\boldsymbol{y}_i - \hat{\boldsymbol{y}}_i\|^2$$
学習則(SGD):
$$W^{(l)} \leftarrow W^{(l)} - \eta \frac{\partial \mathcal{L}}{\partial W^{(l)}}, \quad \boldsymbol{b}^{(l)} \leftarrow \boldsymbol{b}^{(l)} - \eta \frac{\partial \mathcal{L}}{\partial \boldsymbol{b}^{(l)}}$$
ユニバーサル近似定理(Hornik 1989):
$$\forall f \in C(K), \; \forall \varepsilon > 0, \; \exists \text{1-hidden-layer MLP } g : \sup_{\boldsymbol{x} \in K} |f(\boldsymbol{x}) - g(\boldsymbol{x})| < \varepsilon$$
入力 x を1層目の重み W1 とバイアス b1 で線形変換し、ReLUなどの非線形関数 sigma を通し、2層目 W2 で出力に戻す。線形変換だけなら単回帰の延長だが、非線形を挟むことで曲がった関係を表現できる。
| 記号 | 読み方 | MLP での役割 | サイズ・例 |
|---|---|---|---|
| $\mathbf{x} \in \mathbb{R}^{d_{\text{in}}}$ | エックス | 入力ベクトル。 1 サンプル分の特徴量 | SSDSE-B-2026 の 47 県 × 8 特徴の 1 行($d_{\text{in}}=8$) |
| $W^{(l)} \in \mathbb{R}^{d_l \times d_{l-1}}$ | ダブリュー・エル | 第 $l$ 層の重み行列。 前層出力を当層次元に線形変換 | 隠れ層 16 ノード時、 $W^{(1)}$ は $16 \times 8$ |
| $\mathbf{b}^{(l)} \in \mathbb{R}^{d_l}$ | ビー・エル | 第 $l$ 層のバイアス。 平行移動の自由度 | 隠れ層 16 ノードなら 16 次元 |
| $\mathbf{z}^{(l)} = W^{(l)}\mathbf{a}^{(l-1)} + \mathbf{b}^{(l)}$ | ゼット・エル | 第 $l$ 層の「活性化前」値(プリアクティベーション) | 線形和。 ReLU を適用する直前の値 |
| $\phi^{(l)}(\cdot)$ または $\sigma$ | ファイ/シグマ | 活性化関数。 非線形性を入れて「曲がった関係」を表現 | 隠れ層は ReLU、 出力層は softmax/恒等 |
| $\mathbf{a}^{(l)} = \phi^{(l)}(\mathbf{z}^{(l)})$ | エー・エル | 第 $l$ 層の活性(出力)。 次層の入力になる | $\mathbf{a}^{(L)}$ が最終予測 $\hat{\mathbf{y}}$ |
| $\delta^{(l)} = \frac{\partial \mathcal{L}}{\partial \mathbf{z}^{(l)}}$ | デルタ・エル | 誤差信号。 逆伝播で出力層から順に計算 | 勾配 $\partial \mathcal{L}/\partial W^{(l)} = \delta^{(l)} (\mathbf{a}^{(l-1)})^\top$ |
| $L$ | エル | 層数(入力層を 0、 出力層を $L$ とすることが多い) | $L=2$ なら隠れ 1 層、 $L=3$ なら隠れ 2 層 |
例:MNIST 手書き数字認識の典型 MLP 構成
| 層 | ニューロン数 | 活性化 | パラメータ数 |
|---|---|---|---|
| 入力 | 784 (28×28) | − | 0 |
| 隠れ層 1 | 256 | ReLU | 784×256+256 = 200,960 |
| 隠れ層 2 | 128 | ReLU | 256×128+128 = 32,896 |
| 出力 | 10 | Softmax | 128×10+10 = 1,290 |
| 合計 | パラメータ数 ≈ 235,000 | ||
このシンプル構成で MNIST テスト精度 98% 以上。
このコードでやること:SSDSE-B-2026の実特徴量から消費支出 L3221 を予測する小さな MLPRegressor を作り、5分割CVで誤差を見る。
入力例:data/raw/SSDSE-B-2026.csv から2023年の47都道府県を抽出し、Prefecture は文字列、A1101 などの統計列は数値に変換する。
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 | import pandas as pd from sklearn.model_selection import KFold, cross_val_score from sklearn.neural_network import MLPRegressor from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.metrics import r2_score raw = pd.read_csv("data/raw/SSDSE-B-2026.csv", encoding="cp932") df = raw.iloc[1:].copy() df = df[df["SSDSE-B-2026"].astype(str) == "2023"].copy() features = ["A1101", "A1303", "B4101", "G7101", "I510120"] for col in features + ["L3221"]: df[col] = pd.to_numeric(df[col], errors="coerce") X = df[features] y = df["L3221"] mask = X.notna().all(axis=1) & y.notna() X, y = X[mask], y[mask] model = Pipeline([ ("scaler", StandardScaler()), ("mlp", MLPRegressor(hidden_layer_sizes=(8,), activation="relu", solver="lbfgs", alpha=0.1, max_iter=2000, random_state=0)), ]) cv = KFold(n_splits=5, shuffle=True, random_state=42) mae = -cross_val_score(model, X, y, cv=cv, scoring="neg_mean_absolute_error") model.fit(X, y) print(f"CV MAE = {mae.mean():,.2f} (std {mae.std():,.2f})") print(f"train R2 = {r2_score(y, model.predict(X)):.3f}") |
💬 5 分割 CV の MAE は 28,259 円で、学習データの平均をそのまま予測するだけの基準(MAE 18,558 円)より約 1 万円悪い。一方で全 47 県に当て直した train R² は 0.572 あり、手元のデータにはそこそこ合うのに未知の県には外れる、典型的な過学習の形になっている。分割ごとのばらつき(std 11,896 円)も大きく、どの県がテスト側に入るかで結果が大きく揺れる。
実行結果:
結果の読み方:全 47 県に当て直すと北海道のように数円単位で当たる県もあるが、CV MAE はリッジ(19,594 円)より約 8,700 円悪い。47件しかない表データでは、MLPは表現力が強すぎて汎化しにくい典型例になる。
出生率 A4103 を 3 説明変数(A1101 / A1303 / B4101)で予測:
| モデル | パラメータ数 | $R^2$ (train) | 5-fold CV $R^2$ | 学習時間 |
|---|---|---|---|---|
| 線形回帰 | 4 | 0.652 | 0.61 ± 0.09 | 1 ms 未満 |
| Ridge ($\alpha=1.0$) | 4 | 0.652 | 0.62 ± 0.09 | 1 ms 未満 |
| MLP (8,) | 41 | 0.724 | 0.46 ± 0.22 | 約 10 ms |
| MLP (16, 8) | 209 | 0.873 | −1.24 ± 2.18 | 約 35 ms |
| MLP (32, 16, 8) | 801 | 0.905 | −0.55 ± 0.77 | 約 30 ms |
→ 入力・目的変数とも標準化し、 KFold(5, shuffle=True, random_state=42)、 MLP は adam・学習率 0.01・random_state=42 で実測。 パラメータが増えると train $R^2$ は 0.65 → 0.91 と上がるが、 CV $R^2$ は線形回帰・Ridge の 0.61〜0.62 が最良で、 MLP は (8,) でも 0.46、 (16, 8) 以上では負になる。 47 県の SSDSE 規模では線形モデルを基準にし、 MLP を使うなら 1 層・数ユニットに留める。 学習時間は環境依存の目安。
合成データで 2 層 MLP (2→3→1) の順伝播を計算する。
1 2 3 4 5 6 7 8 9 10 11 | import numpy as np x = np.array([1, 2]) W1 = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) b1 = np.array([0.1, 0.1, 0.1]) W2 = np.array([0.1, 0.2, 0.3]) z1 = W1 @ x + b1 a1 = np.maximum(z1, 0) y = W2 @ a1 print(f"z1: {z1}") print(f"a1: {a1}") print(f"y: {y}") |
💬 手計算 (Step 3) 0.84 と Python 出力が完全一致。
このコードでやること:SSDSE-B-2026 から 47 都道府県の総人口 A1101 と高齢人口 A1303 の 2 特徴を入力、 中間層 3 ユニット (ReLU)、 出力 1 ユニットの最小 MLP で消費支出 L3221 を予測する。 重みは事前学習済みと仮定し、 順伝播の各ステップを完全な数値で追う。
入力は単位を「万人」に変換 (1408.6 万人 = 14,086,000 人) し、 さらに 0-1 標準化 (各特徴の min を 0, max を 1 とする線形変換) を行う。 東京都を例に取る:
$$\boldsymbol{z}^{(1)} = W^{(1)} \boldsymbol{x} + \boldsymbol{b}^{(1)}, \quad \boldsymbol{a}^{(1)} = \mathrm{ReLU}(\boldsymbol{z}^{(1)}), \quad y = \boldsymbol{w}^{(2)} \cdot \boldsymbol{a}^{(1)} + b^{(2)}$$
重み (事前学習済みと仮定、 単位は出力 = 万円換算):
→ 東京の実測 L3221 = 341,320 円に対して予測 296,350 円、 誤差は約 45,000 円 (13%)。 隠れ層 3 ユニットの非常に小さなモデルでもそこそこ追従できる。
このコードでやること:SSDSE-B-2026 から東京の A1101 / A1303 を読み、 Step 1-3 の手計算を numpy で完全再現し、 一致を確認する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import pandas as pd import numpy as np raw = pd.read_csv("data/raw/SSDSE-B-2026.csv", encoding="cp932") df = raw.iloc[1:].copy() df = df[df["SSDSE-B-2026"].astype(str) == "2023"] A1101 = pd.to_numeric(df["A1101"], errors="coerce") / 10000 # 万人 A1303 = pd.to_numeric(df["A1303"], errors="coerce") / 10000 x_raw = np.array([A1101.max(), A1303.max()]) # 東京 x_min = np.array([A1101.min(), A1303.min()]) x_max = np.array([A1101.max(), A1303.max()]) x = (x_raw - x_min) / (x_max - x_min) W1 = np.array([[1.20, -0.40], [-0.60, 1.50], [0.80, 0.30]]) b1 = np.array([-0.10, 0.20, 0.05]) w2 = np.array([0.50, 0.40, 0.30]) b2 = 28.5 z1 = W1 @ x + b1 a1 = np.maximum(z1, 0) y = w2 @ a1 + b2 print(f"x = {x.round(2)}, z1 = {z1.round(2)}, a1 = {a1.round(2)}, y = {y:.3f} 万円") |
💬 入力は東京の総人口・高齢人口で、どちらも 47 県の最大値なので min-max 変換後は x = [1, 1] になる。z1 の 3 成分(0.70, 1.10, 1.15)はすべて正なので ReLU はどのユニットも素通しし、出力は 0.35+0.44+0.345+28.5=29.635 万円。予測のほとんどはバイアス 28.5 万円が占め、入力の寄与は 1.1 万円程度しかないので、東京の実測 34.1 万円との差 4.5 万円を埋めるには重みの学習が要る。
💬 手計算 Step 3 の z1 = (0.70, 1.10, 1.15), a1 = (0.70, 1.10, 1.15), y = 29.635 万円と Python 出力が小数 3 桁まで完全一致。 これにより MLP の順伝播 (アフィン → ReLU → アフィン) が「重みと入力の単純な内積の連鎖」であることを実データで確認できた。
SSDSE-B-2026 の 47 都道府県を入力、 消費支出 L3221 を出力として、 隠れ層ユニット数 h を {1, 3, 8, 32, 128} で変化させた 5-fold CV 結果。 sklearn の MLPRegressor で実装、 入力 5 特徴 (A1101, A1303, B4101, G7101, I510120) と目的変数をどちらも標準化 (StandardScaler + TransformedTargetRegressor)、 solver は既定の adam、 max_iter=3000、 random_state=0、 分割は KFold(5, shuffle=True, random_state=42) で固定。 比較の基準として、 学習データの平均をそのまま予測する場合は RMSE 23,289 円・MAE 18,558 円になる。
| 隠れ層構成 | パラメータ数 | 5-fold CV RMSE (円) | 5-fold CV MAE (円) | 分類 |
|---|---|---|---|---|
| (1,) | 8 | 22,082 | 17,980 | RMSE 最小、 平均予測をわずかに上回る程度 |
| (3,) | 22 | 24,462 | 18,243 | 平均予測と同水準 |
| (8,) | 57 | 22,473 | 17,540 | MAE 最小 |
| (32,) | 225 | 29,208 | 22,524 | 過学習 (over-fit) |
| (128,) | 897 | 35,802 | 25,861 | 過学習 (over-fit) |
| (8, 8) | 129 | 37,699 | 27,287 | 2 層化でさらに悪化 |
→ n=47 という小さなサンプルでは隠れ層 1〜8 ユニットが最良で、 それでも平均予測 (RMSE 23,289 円) を 1,000 円ほどしか下回らない。 32 ユニット以上ではパラメータ数 (225〜897) が訓練データ約 38 県を大きく超え、 CV 誤差が急に悪化する。 2 層の (8,8) はパラメータ 129 個で (32,) より少ないのに最も悪く、 小標本では層を重ねること自体が不安定さを増やす。
💬 これは「層の数 × ユニット数」を闇雲に増やせばよいわけではない、 という MLP 設計の核心。 サンプル数 n とパラメータ数 p のバランス (経験則: p < n/10 を目安) を意識することが重要。
このコードでやること:上で順伝播を確認した同じ 2-3-1 MLP に対して、 誤差逆伝播 (バックプロパゲーション) を 1 ステップ手計算する。 入力 = 東京の標準化済み特徴 x = [1.0, 1.0]、 目標 t = 34.132 万円 (東京の実測 L3221 = 341,320 円)、 予測 y = 29.635 万円 (Step 3 の結果) として、 各層の勾配 ∂L/∂W を完全に追う。 これは MLP の学習アルゴリズムの心臓部。
$$L = \tfrac{1}{2}(y - t)^2, \qquad \frac{\partial L}{\partial y} = y - t$$
$$\frac{\partial L}{\partial \boldsymbol{w}^{(2)}} = (y - t) \cdot \boldsymbol{a}^{(1)}, \qquad \frac{\partial L}{\partial b^{(2)}} = (y - t)$$
$$\boldsymbol{\delta}^{(1)} = (y - t) \cdot \boldsymbol{w}^{(2)} \odot \mathrm{ReLU}'(\boldsymbol{z}^{(1)})$$
ReLU の微分は z > 0 で 1、 z ≤ 0 で 0。 ここでは z^(1) = (0.70, 1.10, 1.15) 全て正なので ReLU′ = (1, 1, 1)。
$$\frac{\partial L}{\partial W^{(1)}} = \boldsymbol{\delta}^{(1)} \otimes \boldsymbol{x}, \qquad \frac{\partial L}{\partial \boldsymbol{b}^{(1)}} = \boldsymbol{\delta}^{(1)}$$
x = [1.0, 1.0] なので外積はそのまま δ を縦に並べた行列:
このコードでやること:上記 1 ステップ更新を numpy で完全再現し、 手計算 Step 5 の重み更新値と小数 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 numpy as np x = np.array([1.0, 1.0]) t = 34.132 # 東京の実測 L3221 = 341,320 円 (万円) W1 = np.array([[1.20, -0.40], [-0.60, 1.50], [0.80, 0.30]]) b1 = np.array([-0.10, 0.20, 0.05]) w2 = np.array([0.50, 0.40, 0.30]) b2 = 28.5 # 順伝播 z1 = W1 @ x + b1 a1 = np.maximum(z1, 0) y = w2 @ a1 + b2 # 逆伝播 dy = y - t dw2 = dy * a1 db2 = dy delta1 = dy * w2 * (z1 > 0) dW1 = np.outer(delta1, x) db1 = delta1 # 1 ステップ更新 eta = 0.1 W1_new, b1_new = W1 - eta * dW1, b1 - eta * db1 w2_new, b2_new = w2 - eta * dw2, b2 - eta * db2 print("W1_new =", W1_new.round(4)) print("w2_new =", w2_new.round(4), "b2_new =", round(b2_new, 4)) |
💬 目標を東京の実測 34.132 万円にすると誤差信号は y−t=−4.497 で、学習率 0.1 の 1 ステップで b2 は 28.5→28.9497 と 0.45 万円上がる。出力層の重み w2 は a1 が大きいユニットほど大きく動き(a1=1.15 の第 3 ユニットは 0.30→0.8172)、隠れ層の W1 は入力が [1, 1] なので 2 列とも同じ量だけ動く。更新後の重みで順伝播をやり直すと予測は 29.635 万円から約 32.81 万円に上がり、損失は 10.11 から約 0.88 まで下がる。
💬 手計算 Step 5 の W1_new = [[1.4248, -0.1752], [-0.4201, 1.6799], [0.9349, 0.4349]], w2_new = [0.8148, 0.8947, 0.8172], b2_new = 28.9497 と Python 出力が小数 4 桁まで完全一致(1.42485 のような 5 桁目が 5 の値は、浮動小数点の丸めで 1.4248 と表示される)。 これにより MLP の誤差逆伝播が「連鎖律 (chain rule) で各層の勾配を出力層から入力層へ計算する」というアルゴリズムであることを実値で確認できた。
→ 順伝播・逆伝播・パラメータ更新の 3 つを n エポック繰り返すと MLP は学習する。 これは深層学習フレームワーク (PyTorch / TensorFlow) が「自動微分」で隠蔽している処理だが、 内部では小数値の演算が並んでいるだけ、 ということが見える化できた。
このコードでやること:SSDSE-B-2026 の 2023 年・47 都道府県 (入力 5 特徴 A1101, A1303, B4101, G7101, I510120、 出力 = L3221 消費支出) を train_test_split(test_size=0.3, random_state=0) で train 32 県 / valid 15 県に分け、 入力と目的変数を train で標準化してから学習し、 train RMSE と valid RMSE を円に戻して比べる。 比較の基準として、 train の平均をそのまま予測すると valid RMSE は 19,479 円になる。 下の 2 つの表はこの条件で実際に動かした値である。
| 学習率 η | train RMSE (円) | valid RMSE (円) | 停止までの反復 | 分類 |
|---|---|---|---|---|
| 0.001 | 16,420 | 21,201 | 500 (上限で打ち切り、 未収束) | 遅いが valid は最良 |
| 0.01 | 6,586 | 33,343 | 500 (上限で打ち切り) | 過学習 |
| 0.1 | 6,636 | 28,022 | 123 (損失の改善が止まり停止) | 過学習 |
| 1.0 | 170,528 | 77,415 | 12 (損失が下がらず停止) | NG (学習が壊れる) |
→ η=0.01 と 0.1 は train RMSE を 6,600 円前後まで下げる一方で valid RMSE は 28,000〜33,000 円と平均予測 (19,479 円) より悪く、 32 県を暗記した典型的な過学習になる。 η=0.001 は 500 回では収束しきらないが、 その「学び足りなさ」がかえって valid 21,201 円と 4 つの中で最良になった。 η=1.0 は発散して NaN になるのではなく、 1 歩が大きすぎて train RMSE 170,528 円という的外れな位置で止まる。 valid が 15 県しかないため、 分割の乱数を変えるとこの順位は入れ替わりうる。
| 対策 | 実装 | train RMSE | valid RMSE | train-valid gap |
|---|---|---|---|---|
| なし (基準) | sklearn | 6,636 | 28,022 | 21,386 |
| L2 正則化 alpha=0.01 | sklearn | 5,294 | 30,864 | 25,570 |
| Early Stopping (train の 20% で監視, patience=20) | sklearn | 22,280 | 21,971 | −309 |
| なし (PyTorch 基準、 全バッチ 500 epoch) | PyTorch | 726 | 35,323 | 34,597 |
| Dropout 0.3 | PyTorch | 15,587 | 21,267 | 5,680 |
| L2 (weight_decay=0.01) + Early Stopping + Dropout 0.3 | PyTorch | 22,383 | 20,271 | −2,112 |
→ L2 alpha=0.01 はこのデータでは弱すぎて、 gap は 21,386 → 25,570 円とむしろ広がった。 Early Stopping は 28 回目で止まって gap をほぼ 0 にし、 Dropout 0.3 は PyTorch 基準の gap 34,597 円を 5,680 円まで縮めた。 3 つを組み合わせると valid RMSE 20,271 円が表の中で最良だが、 それでも平均予測 19,479 円には届かない。 どの対策も「過学習を止める」ことはできても、 32 県・5 特徴から消費支出を当てる力そのものは MLP にほとんど無い。
💬 SSDSE-B-2026 は n=47 と小さく、 valid 15 県の RMSE は 1 県の外れで数千円動く。 正則化の効果を主張するなら、 分割の乱数を変えた複数回の結果や 5-fold CV で確かめ、 平均予測という最低ラインと必ず並べて読む。
比較の条件:上の学習率の表と同じ 5 特徴 → L3221 (消費支出) の予測で、 同じ train 32 県 / valid 15 県の分割と同じ標準化を使い、 活性化関数だけを ReLU / tanh / Sigmoid (logistic) / Identity に変えた。 sklearn は MLPRegressor(hidden_layer_sizes=(32,), solver='adam', learning_rate_init=0.001, alpha=0, max_iter=500, random_state=0)、 LeakyReLU は sklearn に無いので PyTorch で同じ構成 (隠れ層 32、 Adam lr=0.001、 全バッチ 500 epoch、 torch.manual_seed(0)) を組み、 同条件の PyTorch ReLU と並べた。 どれも実際に動かした値で、 平均予測の valid RMSE は 19,479 円。
| 活性化関数 | 実装 | train RMSE (円) | valid RMSE (円) | 停止までの反復 | 備考 |
|---|---|---|---|---|---|
| ReLU | sklearn | 16,420 | 21,201 | 500 (上限) | 学習率の表の η=0.001 の行と同じ |
| tanh | sklearn | 18,210 | 19,712 | 500 (上限) | valid は 4 つの中で最小 |
| Sigmoid (logistic) | sklearn | 22,079 | 20,206 | 397 (損失の改善が止まり停止) | train が最も下がらない |
| Identity (線形) | sklearn | 21,875 | 20,241 | 186 (損失の改善が止まり停止) | LinearRegression は 21,677 / 20,872 |
| ReLU | PyTorch | 16,954 | 22,739 | 500 epoch | PyTorch 側の基準 |
| LeakyReLU (α=0.01) | PyTorch | 16,953 | 22,800 | 500 epoch | ReLU とほぼ同じ |
→ valid RMSE は tanh の 19,712 円が最小で、 Sigmoid 20,206・Identity 20,241・ReLU 21,201 円と続くが、 どれも平均予測 19,479 円に届かない。 4 つの差は最大でも約 1,500 円で、 valid 15 県では 1 県の外れで動く幅に収まる。 ReLU は train を 16,420 円まで下げたぶん valid が悪く、 表現力がそのまま 32 県への過適合になっている。 Identity は 186 回で止まり LinearRegression (train 21,677 / valid 20,872 円) に近い位置に来る。 隠れ層があっても活性化が線形なら全体は線形モデルにすぎない。 PyTorch では LeakyReLU 22,800 円と ReLU 22,739 円がほぼ同じで、 このデータでは dying ReLU は問題になっていない。
💬 「隠れ層は ReLU、 二値出力のみ Sigmoid」は深い DNN での経験則で、 Sigmoid の勾配消失が効いてくるのは層を重ねたとき。 隠れ層 1 層・47 県のこのデータでは Sigmoid でも学習でき、 活性化関数の違いは valid 15 県の誤差の範囲に収まった。 活性化関数を選ぶ前に、 平均予測を超えられるだけの情報が特徴量にあるかを確かめる。
同じ分割・同じ標準化・隠れ層 (32,) の ReLU で、 重み更新アルゴリズム (オプティマイザ) だけを変えた実測値。 sklearn は alpha=0, max_iter=500, random_state=0 共通(sklearn の sgd は既定で Nesterov 型の momentum を使うので、 素朴な Momentum の行は nesterovs_momentum=False を指定した。 既定のままだと train 15,642 / valid 24,675 円)、 RMSprop と AdamW は sklearn に無いので上の PyTorch 構成 (全バッチ 500 epoch) で動かした。 sklearn の MLPRegressor の既定は solver='adam' である。
| オプティマイザ | 実装 | train RMSE (円) | valid RMSE (円) | 停止までの反復 | 特徴 |
|---|---|---|---|---|---|
| SGD (lr=0.01, momentum=0) | sklearn | 20,232 | 19,731 | 500 (上限) | 素朴、 遅い、 ハイパラ感受性高 |
SGD + Momentum (0.9、 lr=0.01、 nesterovs_momentum=False) | sklearn | 15,712 | 24,355 | 500 (上限) | SGD を加速 |
| Adam (lr=0.001、 sklearn の既定) | sklearn | 16,420 | 21,201 | 500 (上限) | Momentum + RMSprop の融合 |
| L-BFGS (solver='lbfgs') | sklearn | 22 | 38,962 | 471 | 準ニュートン法、 小データで速い |
| Adam (lr=0.001) | PyTorch | 16,954 | 22,739 | 500 epoch | PyTorch 側の基準 |
| RMSprop (lr=0.001) | PyTorch | 15,329 | 25,044 | 500 epoch | 勾配の二乗移動平均で正規化 |
| AdamW (lr=0.001, weight_decay=0.01) | PyTorch | 16,964 | 22,707 | 500 epoch | L2 を Adam から分離 |
→ L-BFGS は 471 回で train RMSE 22 円と 32 県をほぼ完全に暗記し、 valid 38,962 円は表の中で最悪。 小データで収束が速く強力なぶん、 正則化 (alpha=0) なしでは過学習が最も極端に出る。 valid が最小なのは素朴な SGD の 19,731 円だが、 これは 500 回で train が 20,232 円までしか下がらない「学び足りなさ」によるもので、 学習率の表の η=0.001 と同じ現象。 Momentum (24,355 円) や RMSprop (25,044 円) のように速く下る方法ほど train が下がって valid が悪化する。 AdamW の weight_decay=0.01 は PyTorch Adam の 22,739 円を 22,707 円にする程度で、 ほとんど効いていない。
💬 このデータでは「どのオプティマイザが良いか」より「どこまで下らせるか」が valid を決めている。 小データで solver='lbfgs' を使うなら alpha を上げるか、 early_stopping=True を指定できる adam / sgd で止めどきを管理する。 どの組み合わせも平均予測 19,479 円をはっきりとは下回らず、 valid は 15 県・1 回の分割なので、 順位を主張するなら 5-fold CV など複数の分割で確かめる。 大規模 DNN では Adam がデファクト標準で、 本ページの例も Adam を基準にしている。
最小コードで動かしてみる例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import torch import torch.nn as nn class MLP(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential( nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 128), nn.ReLU(), nn.Linear(128, 10), ) def forward(self, x): return self.net(x) model = MLP() |
このコードでやること:同じ特徴量で線形リッジとMLPを比較し、複雑なモデルが常に勝つわけではないことを確認する。
入力例:同じSSDSE-B-2026の実データを用いる。必要な列だけを取り出し、列名を明示してから処理する。
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 pandas as pd from sklearn.model_selection import KFold, cross_val_score from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import RidgeCV from sklearn.neural_network import MLPRegressor # X, y はSSDSE-B-2026から作った実データ。合成データは使わない。 features = ["A1101", "A1303", "B4101", "G7101", "I510120"] raw = pd.read_csv("data/raw/SSDSE-B-2026.csv", encoding="cp932") df = raw.iloc[1:].copy() df = df[df["SSDSE-B-2026"].astype(str) == "2023"].copy() for col in features + ["L3221"]: df[col] = pd.to_numeric(df[col], errors="coerce") X, y = df[features], df["L3221"] mask = X.notna().all(axis=1) & y.notna() X, y = X[mask], y[mask] cv = KFold(n_splits=5, shuffle=True, random_state=42) models = { "ridge": Pipeline([("scaler", StandardScaler()), ("ridge", RidgeCV(alphas=[0.1, 1, 10, 100]))]), "mlp": Pipeline([("scaler", StandardScaler()), ("mlp", MLPRegressor(hidden_layer_sizes=(8,), solver="lbfgs", alpha=0.1, max_iter=2000, random_state=0))]), } for name, model in models.items(): score = -cross_val_score(model, X, y, cv=cv, scoring="neg_mean_absolute_error") print(name, round(score.mean(), 2)) |
💬 同じ 5 特徴・同じ分割で比べると、リッジの MAE 19,594 円に対して MLP は 28,259 円と 8,700 円近く悪い。リッジでさえ平均予測(18,558 円)に届いておらず、この 5 特徴だけでは県の消費支出はほとんど説明できない。47 行しかないデータでは、パラメータ 57 個の MLP より係数 5 個の線形モデルの方が安定するので、MLP を使う前に線形モデルを基準として置いておく意味がここにある。
実行結果:
結果の読み方:MLPを使う理由は「ニューラルネットだから」ではなく、非線形性・交互作用・十分なデータ量があるからである。比較ベースラインを必ず置く。
🎯 このコードでやること:SSDSE-B-2026 の出生率 A4103 を 3 説明変数で予測する基本 MLP を学習する。
📥 入力データ (SSDSE-B-2026 から抽出):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import pandas as pd from sklearn.neural_network import MLPRegressor from sklearn.preprocessing import StandardScaler from sklearn.model_selection import cross_val_score, KFold df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023] X = d[['A1101', 'A1303', 'B4101']].values.astype(float) y = d['A4103'].values Xs = StandardScaler().fit_transform(X) ys = StandardScaler().fit_transform(y.reshape(-1, 1)).ravel() mlp = MLPRegressor(hidden_layer_sizes=(16, 8), activation='relu', max_iter=5000, random_state=42, learning_rate_init=0.01).fit(Xs, ys) print(f'train R² = {mlp.score(Xs, ys):.4f}') # 県コード順に並んだまま 5 分割すると九州・沖縄だけのテスト分割ができるので、シャッフルしてから分ける cv5 = KFold(n_splits=5, shuffle=True, random_state=42) cv = cross_val_score(MLPRegressor(hidden_layer_sizes=(16,8), activation='relu', max_iter=5000, random_state=42, learning_rate_init=0.01), Xs, ys, cv=cv5, scoring='r2') print(f'5-fold CV R² = {cv.mean():.3f} ± {cv.std():.3f}') |
📤 実行すると次の出力が得られる:
💬 結果の読み方:train $R^2=0.87$ なのに CV $R^2=-1.24$(平均を答えるより悪い)。 これは過学習そのもので、47 サンプルに対し 209 パラメータは多すぎる。 しかも fold ごとのばらつき(±2.18)が平均より大きく、推定がまったく安定していない。 同じ分割で線形回帰は CV $R^2=0.61$ を出すので、 特徴量に予測力が無いのではなく MLP が 38 県の学習データを覚え込んでいる。 早期停止・正則化・そもそも層を小さくすることが必須。
🎯 このコードでやること:隠れ層サイズを変えて 5-fold CV を実行、 最適サイズを探索する。 過学習の境界を見極める。
📥 入力データ (SSDSE-B-2026 から抽出):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from sklearn.neural_network import MLPRegressor from sklearn.model_selection import cross_val_score, KFold cv5 = KFold(n_splits=5, shuffle=True, random_state=42) # 県コード順の偏りを避ける configs = [(8,), (16,), (16, 8), (32, 16), (32, 16, 8), (64, 32, 16)] print(f'{"hidden":18s} CV R² train R²') for h in configs: cv = cross_val_score(MLPRegressor(hidden_layer_sizes=h, activation='relu', max_iter=5000, random_state=42, learning_rate_init=0.01), Xs, ys, cv=cv5, scoring='r2') mlp = MLPRegressor(hidden_layer_sizes=h, activation='relu', max_iter=5000, random_state=42, learning_rate_init=0.01).fit(Xs, ys) print(f'{str(h):18s} {cv.mean():+.3f}±{cv.std():.3f} ' f'{mlp.score(Xs, ys):+.3f}') |
📤 実行すると次の出力が得られる:
💬 結果の読み方:train $R^2$ は (8,) の 0.724 から 0.87〜0.92 へ上がるが、 CV $R^2$ が正なのは (8,) の +0.462 だけで、 16 ユニット以上はどれも −0.44〜−1.24 と平均予測にも負ける。 その (8,) でも同じ分割の線形回帰(CV $R^2=0.61$)に届かないので、 47 県では 1 層 8 ユニット程度が上限で、 深さ・幅を増やすほど未知の県で外れる。
🎯 このコードでやること:PyTorch で同じ MLP を組み Dropout (0.2) と Early Stopping を加える。 train/val の差を縮める。
📥 入力データ (SSDSE-B-2026 から抽出):
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 | import torch, torch.nn as nn, numpy as np
from sklearn.model_selection import train_test_split
torch.manual_seed(42)
X_tr, X_va, y_tr, y_va = train_test_split(Xs, ys, test_size=0.3, random_state=42)
X_tr_t = torch.tensor(X_tr, dtype=torch.float32)
y_tr_t = torch.tensor(y_tr, dtype=torch.float32).view(-1, 1)
X_va_t = torch.tensor(X_va, dtype=torch.float32)
y_va_t = torch.tensor(y_va, dtype=torch.float32).view(-1, 1)
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(3, 16), nn.ReLU(), nn.Dropout(0.2),
nn.Linear(16, 8), nn.ReLU(), nn.Dropout(0.2),
nn.Linear(8, 1))
def forward(self, x): return self.net(x)
model = MLP()
opt = torch.optim.Adam(model.parameters(), lr=0.01)
loss_fn = nn.MSELoss()
best_va = float('inf'); patience = 0
for epoch in range(500):
model.train(); opt.zero_grad()
loss_fn(model(X_tr_t), y_tr_t).backward(); opt.step()
model.eval()
with torch.no_grad():
va = loss_fn(model(X_va_t), y_va_t).item()
if va < best_va: best_va, patience = va, 0
else: patience += 1
if patience > 30: break
print(f'停止 epoch: {epoch+1}, val loss = {best_va:.5f}') |
📤 実行すると次の出力が得られる:
💬 結果の読み方:Dropout 0.2 + Early Stopping (patience=30) で 157 epoch で停止し、 その時点の最良 val loss は 0.49876(標準化した目的変数の MSE なので、 分散 1.0 に対して約半分まで説明できた程度)。 早期停止をしない次のコード 4 では同じ設定で epoch 300 まで回すと val loss が 1.0498 まで悪化するので、 この停止が効いていることが確認できる。 torch.manual_seed(42) と random_state=42 を指定しているので、 この数値は何度実行しても再現する。
🎯 このコードでやること:学習中の train / val 損失を毎 epoch 記録し、 過学習が始まる時点を特定する。
📥 入力データ (SSDSE-B-2026 から抽出):
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 | import torch, torch.nn as nn
torch.manual_seed(42)
class MLPraw(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(3, 16), nn.ReLU(),
nn.Linear(16, 8), nn.ReLU(),
nn.Linear(8, 1))
def forward(self, x): return self.net(x)
model = MLPraw()
opt = torch.optim.Adam(model.parameters(), lr=0.01)
hist_tr, hist_va = [], []
for epoch in range(300):
model.train(); opt.zero_grad()
tr = nn.MSELoss()(model(X_tr_t), y_tr_t)
tr.backward(); opt.step()
with torch.no_grad():
va = nn.MSELoss()(model(X_va_t), y_va_t).item()
hist_tr.append(tr.item()); hist_va.append(va)
# 過学習開始 epoch 特定
import numpy as np
diff = np.array(hist_va) - np.array(hist_tr)
overfit_epoch = int(np.argmax(diff[20:]) + 20)
print(f'epoch 10: train={hist_tr[9]:.4f} val={hist_va[9]:.4f}')
print(f'epoch 100: train={hist_tr[99]:.4f} val={hist_va[99]:.4f}')
print(f'epoch 300: train={hist_tr[299]:.4f} val={hist_va[299]:.4f}')
print(f'最大 train-val 乖離 epoch: {overfit_epoch}') |
📤 実行すると次の出力が得られる:
💬 結果の読み方:epoch 100 までは train/val が同調(0.3042 / 0.4899)、 100 → 300 で train は 0.2168 まで下がり続けるのに val は 1.0498 へ 倍以上に悪化 → 過学習。 乖離が最大になるのは epoch 282 で、 早期停止の妥当な閾値は val が底を打つ epoch 100〜160 付近。
StandardScaler や MinMaxScaler で 入力スケールを揃える のが必須。 SSDSE の総人口(10⁶ オーダ)と気温(10¹ オーダ)を混ぜると、 重みの初期勾配が人口側に支配されて気温の寄与が学習されない。random_state=42 を固定したうえで 複数 seed で平均 を取り、 「seed 1 つの偶然」を排除する。MLP を中心に、 単純パーセプトロン・隠れ層・活性化関数 (ReLU/GELU)・正則化 (Dropout/BatchNorm)・損失関数・誤差逆伝播・最適化 (Adam) を整理した概念マップ。
MLP は SSDSE-B-2026 の表データ予測でも使えるが、 ランダムフォレストや LightGBM と比べて前処理 (標準化) が必須かつハイパラ調整の手間が大きい。 47 件 × 110 列では過学習しやすく、 ドロップアウト + 早期打ち切りが必須。
MLP (多層パーセプトロン) は単独の手法というより、 上流の入力標準化、 並列の活性化関数 (ReLU / Sigmoid / GELU) + Dropout + BatchNorm、 下流の損失関数 (CrossEntropy / MSE) + Optimizer (Adam) と組み合わせて初めて学習する。 DL の基本構成要素。
SSDSE-B-2026 で MLP を試す場合、 上流で 35 列を StandardScaler で標準化、 中段で隠れ層 [64, 32] + ReLU、 下流で MSE 損失 + Adam (lr=0.001)、 50 epoch 学習、 という典型構成。 47 サンプルでは過学習しやすいので Dropout(0.3) を入れる。
MLP を採用するか他の手法に切り替えるかは、 (1) データの構造 (表形式 / 画像 / 時系列)、 (2) サンプル数、 (3) 解釈性の要求度、 で判断する。 表形式 + n < 10,000 なら勾配ブースティングが先、 画像なら CNN、 時系列なら RNN/LSTM が定石。
SSDSE-B-2026 (47 県) のような小規模表形式データでは、 MLP より線形回帰や Random Forest の方が安定する場合が多い。 MLP を使うなら隠れ層 1 層・unit 数 16-32、 L2 正則化、 5-fold CV で過学習を抑える。 本ページの消費支出 L3221 予測でも、 RidgeCV の 5 分割 CV MAE 19,594 円に対し MLP は 32,323 円と、 線形モデルの方が良い結果になることは珍しくない (n が少ないため)。
MLP の実装は ① データ前処理 (標準化・train/val 分割)、 ② モデル定義 (層数・幅・活性化関数)、 ③ 学習 (損失・オプティマイザ・epoch)、 ④ 評価 (検証損失・テスト精度)、 ⑤ 推論 (新規データへの適用) の 5 段階。 47 件の都道府県データなら 1 epoch が数ミリ秒で済むため、 100〜500 epoch を回しても 1 秒以内。
確認順序は、 (1) 入力の単位と分布、 (2) 標準化の有無、 (3) ネットワーク構造、 (4) 学習曲線 (train vs val loss)、 (5) 汎化性能 (テスト RMSE/精度) の 5 点。 これを欠かさずレポートに書くと、 査読者・上司が結果を再現・解釈できる。
MLP の学習曲線 (epoch vs loss) が分析の鍵。 訓練損失と検証損失を同じ図にプロットすると、 ① 両方下がる: 健全に学習中、 ② 訓練だけ下がる: 過学習、 ③ 両方下がらない: 学習率/初期化が不適、 ④ 検証損失が振動: バッチサイズが小さすぎ、 を即座に判定できる。
47 件は小規模なため、 学習曲線が非常にノイジーになる。 5-fold CV で平均化した曲線を読むのが正攻法。 PyTorch なら matplotlib で plt.plot(losses)、 Weights & Biases で記録すると複数試行を一括可視化できる。
隠れ層 0 の MLP は実はロジスティック回帰 (二値分類) または線形回帰 (回帰) と数学的に等価。 つまり MLP はロジスティック回帰の自然な拡張。 隠れ層を 1 層追加することで非線形決定境界を表現できるようになる (例: XOR 問題が解ける)。
SSDSE-B-2026 で「47 件の二値分類」をするとき、 まずロジスティック回帰でベースラインを取り、 隠れ層 8〜16 の MLP で改善幅を確認するのが定石。 改善幅が 0.05 未満なら MLP の追加コストに見合わない可能性が高い。 ロジスティック回帰 / 線形回帰 も参照。
47 都道府県の MLP は CPU で十分。 GPU が必要になるのは、 ① N > 10000 件のバッチ並列、 ② 大規模画像/テキスト処理、 ③ Transformer の Attention 計算、 などのケース。 表データの小規模 MLP は CPU で 1 epoch 数ミリ秒、 全学習が数秒で完了する。
GPU を使う場合は model.to('cuda') と data.to('cuda') を忘れずに。 ただし 47 件をデータ転送するオーバーヘッドが計算時間を上回ることもあるので、 ベンチマークで判断する。 colaboratory・Kaggle Notebook・SageMaker などのクラウド GPU が便利。
学習後のモデルは torch.save(model.state_dict(), 'model.pth') で保存、 model.load_state_dict(torch.load('model.pth')) でロード。 推論時は model.eval() を呼んで Dropout・BatchNorm を推論モードに切り替える (これを忘れると結果が再現しない)。
本番運用では ONNX エクスポート (torch.onnx.export) でフレームワーク非依存形式に、 さらに TensorRT で推論を 10 倍速化するパスが標準。 SSDSE-B-2026 の小規模 MLP なら ONNX で 47 件推論が 1ms 未満。
MLP のハイパラ (層数・幅・lr・dropout・batch_size) を grid search で全探索すると組合せ爆発する。 4 ハイパラ × 各 5 値 = 625 通り。 random search は同じ計算量で広範囲をカバーでき、 統計的に grid と同等以上 (Bergstra 2012)。 Optuna (Akiba 2019) はベイズ最適化で 100 試行以内に良いハイパラを見つける。
47 件データの MLP なら 5-fold CV を内側に組んだ Optuna が 30 分以内で収束。 ハイパラ探索は学習と分離して再利用可能にする (search → final training の 2 段階)。 ハイパラ調整 / ハイパーパラメータ を参照。
MLP は「ブラックボックス」と呼ばれることがあるが、 SHAP (Lundberg 2017) / LIME (Ribeiro 2016) で各特徴量の寄与度を可視化できる。 SSDSE-B-2026 で出生率予測の MLP なら、 SHAP で「年平均気温が +0.5、 人口密度が -0.3 寄与」のように分解できる。
Permutation Importance も簡便で、 特徴量を 1 列ずつシャッフルして性能劣化を測る。 scikit-learn の permutation_importance で 5 行で実装。 SHAP は計算コストが高いため、 47 件なら全件、 大規模ならサンプリングで近似。
MLP の歴史は ① 1957 Rosenblatt の Perceptron (単層、 XOR 解けない)、 ② 1969 Minsky/Papert の批判で AI 冬の時代、 ③ 1986 Rumelhart の backpropagation で多層が学習可能に、 ④ 1989 Cybenko の Universal Approximation Theorem、 ⑤ 2012 AlexNet で深層学習ブーム、 と進んだ。
Rosenblatt の Perceptron はハードウェアで実装された (Mark I Perceptron 機)。 当時は 400 ピクセル画像の分類が限界。 現在の Transformer (Vaswani 2017) は MLP を Attention で繋いだ構造で、 MLP は今も deep learning の基本ブロックである。
MLP は表データ向け、 CNN は画像向け (空間的相関を畳み込みで捉える)、 RNN/LSTM/Transformer は系列データ向け (時間的相関を捉える)。 SSDSE-B-2026 のような 47 県表データは MLP が最適。 都道府県の地理的隣接を考慮するなら Graph Neural Network (GNN)。
複合構造も可能: CNN で画像特徴を抽出→MLP で分類、 RNN で系列要約→MLP で予測、 など MLP が「最後の分類器」として使われるのが一般的。 CNN / RNN / Transformer で詳細。
MLP の現場で多発する失敗: ① N=47 で隠れ層 1000 ニューロンを組んで過学習、 ② 入力を標準化せず学習が発散、 ③ sigmoid 隠れ層で深層化して勾配消失、 ④ シードを固定せず結果が再現できない、 ⑤ Dropout を推論時にもオンのまま (model.eval() 忘れ)。
対策は ① パラメータ数 < サンプル数の経験則、 ② StandardScaler を必ず通す、 ③ 隠れ層は ReLU、 ④ torch.manual_seed(42) 必須、 ⑤ 推論前に model.eval()。 過学習 / 正則化 で対策を深掘り。
ここでは MLP の核心の一つ、 ユニバーサル近似定理(万能近似定理)を手で確かめる。 主張はこうだ ── 隠れ層を 1 つ持ち、 ReLU のような非線形ユニットを十分な数だけ並べれば、 任意の連続関数を好きな精度で近似できる(Cybenko 1989, Hornik 1991)。 下のスライダーで 隠れユニット数 H を 1〜20 で増減させると、 各ユニットが担う「ReLU の折れ線パーツ」が重なり合って、 灰色の破線で描いた架空のターゲット曲線に緑の予測がだんだん吸い付いていく様子が見える。
このデモの数理(手抜きなし): 1 入力 1 出力の MLP を 基底 [ 1, x, ReLU(x−t₁), …, ReLU(x−t_H) ] として構成する。 隠れ第 k ユニットは折れ点(キンク)を t_k = k/(H+1) に固定した ReLU、 出力層の重み w は 最小二乗法(正規方程式+微小リッジ 1e−6 を Gauss 消去で解く)で決定的に求める。 乱数最適化は使わず、 同じ H・同じターゲットなら常に同じ重みになる。 訓練点は 24 個の架空サンプル(決定的な擬似乱数で ±0.07 のノイズを付与、 実データではない)。
グラフ上をドラッグ(タッチ可)すると、 その x での真の値と MLP 予測の差が読めます。
H を 1 にすると、 予測は「折れ点 1 つのくの字」しか作れず、 曲線を全く追えない(表現力不足=underfit)。 H を 4〜6 へ増やすと、 ReLU の折れ点が曲線の曲がり角に配置され、 区分線形の折れ線がなめらかな曲線に見えるほど密に近づく。 「各ユニットの寄与を色分け表示」をオンにすると、 1 本 1 本の ReLU パーツ(ある点から立ち上がる直線)が積み重なって全体の形を作っていることが分かる。 これがユニバーサル近似の実体だ ── 非線形ユニットは「部品となる基底関数」であり、 出力層の重み付き和がそれらを合成する。
定理が保証するのは「そういう重みが存在する」ことだけで、 勾配降下でそこに到達できる保証も、 必要なユニット数が現実的である保証もない。 実際、 H を 16〜20 まで増やすと訓練MSE は下がり続けるのに、 テストMSE(真の曲線に対する誤差)は途中で底を打って再び悪化する(スライダーで確認できる U 字)。 これは折れ点が増えすぎて、 曲線ではなく架空ノイズの上下動まで拾ってしまう過学習(overfit)だ。 「多ければ良い」ではなく、 データ量に対して適切な表現力(ここでは 24 点に対し H≈5〜8)を選ぶことが本質である。 また 1 次元では H に比例で足りるが、 入力次元が上がると必要ユニット数は指数的に爆発する(次元の呪い)。
このデモは「幅(ユニット数)を増やす」浅い近似だが、 現代の深層学習が層を深くするのには理由がある。 同じ表現力を得るのに、 浅いネットが指数的な幅を要する関数でも、 深いネットは多項式的な幅で表せる(表現効率)ことが知られている(例: のこぎり波状の関数は層を重ねると折れ点が掛け算的に増える)。 つまり普遍近似定理は「1 隠れ層で十分」を数学的に言うだけで、 実用的な効率は深さが担う。 XOR を隠れ層の表現学習で解く話は 3層パーセプトロン、 単層の学習則の限界は パーセプトロン、 ReLU など非線形の役割は 活性化関数、 全体像は ニューラルネットワーク と 深層学習 を参照。
この増補セクションは、「MLP は表現力が高いから強い」という素朴な期待を、data/raw/SSDSE-B-2026.csv(cp932、skiprows=[1]、2023 年の 47 都道府県)の実測値で検証する。合成データ(np.random)は一切使わない。結論を先に言うと、47 件という小標本では、MLP は交差検証で崩壊し、線形モデル(Ridge)や勾配ブースティング(GBDT)に明確に劣る。これは本ページ冒頭の「MLP は CNN・Transformer の基本部品」という主張と矛盾しない ── MLP の真価は大標本・高次元で出るのであり、n=47 の表データでは道具の選択を誤ると逆効果になる、という補足である。
MLP は入力層・隠れ層・出力層を全結合でつなぎ、各層で線形変換のあとに非線形活性化(ReLU 等)を挟むことで、線形回帰では表せない複雑な曲面を近似する。学習は出力の誤差を 誤差逆伝播法 で各層へ配分し、勾配降下法 で重みを更新する。これは パーセプトロン を多層化し非線形性を与えたもので、全体像は ニューラルネットワーク・深層学習 に連なる。だが 普遍近似定理が保証するのは「そういう重みが存在する」ことだけであり、47 個の観測からその重みを推定できるとは言っていない。パラメータ数が標本数を大きく超えると、モデルは訓練点を「暗記」して未知の県で外す。下の実測はまさにこれを示す。
同じ 47 行・同じ特徴量・同じ 5-fold CV(KFold(shuffle=True, random_state=42)、入力は 標準化 済み)で、4 つのモデルを比べた実測値である。train R² は全 47 件で学習した当てはまり、CV R² は未知の県に対する汎化性能を表す。
ケース A: 目的変数 = 消費支出 L3221(説明変数 = 総人口 A1101 / 高齢人口 A1303 / 出生数 A4101 / 出生率 A4103 / 年平均気温 B4101)
| モデル | train R² | 5-fold CV R² | CV MAE (円) | 読み方 |
|---|---|---|---|---|
| Ridge (線形) | +0.216 | +0.159 | 17,395 | CV R² は最良。過学習も小さい |
| MLP (16, 8) | −135.4 | −171.4 | 277,688 | 収束せず崩壊。線形の約16倍の誤差 |
| MLP (64, 32) | −22.2 | −33.1 | 104,785 | 幅を広げても崩壊は変わらず |
| GBDT | +0.995 | −0.131 | 17,264 | train を暗記するが CV は負(=過学習) |
読み方: CV R² が負とは「県平均を予測に使うより悪い」という意味。MLP は train R² すら負 ── これは MLPRegressor が目的変数 y をスケールしないため、L3221(数十万円オーダ)に対し既定の学習率では規定反復(max_iter=3000)内に収束できず発散気味になる、小データ特有の病理である。GBDT は train R²=+0.995 と暗記するが CV は負で、「訓練当てはまりの良さ」は汎化の保証にならないことを端的に示す。この課題では L3221 とこれら特徴量の相関自体が弱く、Ridge の CV R²=+0.159 が事実上の上限に近い(CV MAE は GBDT の 17,264 円が Ridge の 17,395 円をわずかに下回るが、CV R² は負)。
ケース B: 目的変数 = 出生率 A4103(説明変数 = 総人口 A1101 / 高齢人口 A1303 / 出生数 A4101 / 年平均気温 B4101 / 消費支出 L3221)
| モデル | train R² | 5-fold CV R² | 読み方 |
|---|---|---|---|
| Ridge (線形) | +0.726 | +0.611 | 最良の汎化。train と CV の差も小さい |
| MLP (16, 8) | −0.760 | −3.161 | 崩壊。線形に遠く及ばない |
| MLP (64, 32) | +0.478 | −3.181 | train 0.48 → CV −3.18 の巨大な過学習ギャップ |
| GBDT | +0.996 | +0.386 | train 暗記。CV は線形に劣る |
読み方: ケース B は特徴量に予測力があり Ridge の CV R²=+0.611 と健全だが、それでも MLP は CV R²=−3 台に崩壊する。特に MLP(64,32) の train R²=+0.478 に対し CV R²=−3.181 という開きは、47 点を覚えて未知県で真逆を予測する典型的な 過学習 の姿である。結論: n=47 の表データでは線形モデルが最良、次点で GBDT、MLP は最下位。これは「表形式・小標本では MLP は勾配ブースティングにも線形にも劣りうる」という一般則の実データ確認になっている。
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 | import pandas as pd, numpy as np from sklearn.linear_model import RidgeCV from sklearn.neural_network import MLPRegressor from sklearn.ensemble import GradientBoostingRegressor from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.model_selection import cross_val_score, cross_val_predict, KFold from sklearn.metrics import mean_absolute_error # 2023 年・47 都道府県のみ抽出(英字コードを使うので skiprows=[1]) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023] # -> 47 行 cv = KFold(n_splits=5, shuffle=True, random_state=42) cases = { 'A: 消費支出 L3221': ('L3221', ['A1101', 'A1303', 'A4101', 'A4103', 'B4101']), 'B: 出生率 A4103': ('A4103', ['A1101', 'A1303', 'A4101', 'B4101', 'L3221']), } # 入力は標準化する。MLP は目的変数をスケールしない(MLPRegressor の既定のまま) models = { 'Ridge (線形)': make_pipeline(StandardScaler(), RidgeCV(alphas=np.logspace(-3, 3, 20))), 'MLP (16, 8)': make_pipeline(StandardScaler(), MLPRegressor(hidden_layer_sizes=(16, 8), max_iter=3000, random_state=42)), 'MLP (64, 32)': make_pipeline(StandardScaler(), MLPRegressor(hidden_layer_sizes=(64, 32), max_iter=3000, random_state=42)), 'GBDT': GradientBoostingRegressor(random_state=42), } for case, (target, feats) in cases.items(): X = d[feats].astype(float).values y = d[target].astype(float).values print(f'ケース {case}') for name, m in models.items(): train_r2 = m.fit(X, y).score(X, y) cv_r2 = cross_val_score(m, X, y, cv=cv, scoring='r2').mean() cv_mae = mean_absolute_error(y, cross_val_predict(m, X, y, cv=cv)) print(f' {name:12s} train R2 = {train_r2:+8.3f} CV R2 = {cv_r2:+8.3f} CV MAE = {cv_mae:,.3f}') |
💬 結果の読み方: 上の 2 つの表はこの出力をまとめたもの。ケース A の MLP (16, 8) は train R² すら −135.4 で、目的変数の消費支出(約 30 万円)をスケールしないまま既定の学習率で学習すると、3000 回の反復を終えても全 47 県への予測の平均は約 1.8 万円で、実測平均 29.6 万円の水準に届かない。ケース B は目的変数が 1 前後なので train R² は −0.76〜+0.48 まで戻るが、CV R² は −3 台で Ridge の +0.611 に遠く及ばない。もし MLP を使うなら (1) 目的変数も標準化する、(2) 隠れ層を 1 層 8 ユニット程度に絞る、(3) 正則化(alpha)と早期停止を効かせる、(4) それでも Ridge/GBDT に勝てなければ採用しない ── が実務の順序である。
普遍近似定理(Cybenko 1989, Hornik 1991)は「1 隠れ層 + 十分な非線形ユニットで任意の連続関数を近似できる」と述べるが、必要なユニット数・必要なデータ量は保証しない。実務で MLP を支えるのは、ReLU(ReLU / 活性化関数)による勾配消失の緩和、Adam による学習率の自動調整、ドロップアウト・重み減衰(L2)による 正則化、バッチ正規化による層間スケールの安定化、そして He 初期化 といった一連の工夫である。これらが真価を発揮するのは標本数が数千〜数百万で、入力が高次元(画像・音声・埋め込み)のときだ。深さは同じ表現力をより少ないパラメータで実現する(深さ vs 幅の効率)が、n=47 では深くするほど過学習ギャップが開く(上のケース B が実証)。表形式・小標本という SSDSE-B の土俵では、勾配ブースティング(GBDT)や線形モデルが構造的に有利で、上の実測もその通りになった。MLP を学ぶ意義は、3層パーセプトロン から 深層学習 へ続く基本ブロックとしての理解にあり、「47 県の表を当てる道具」としてではない。
注: 上の数値はすべて data/raw/SSDSE-B-2026.csv(2023 年 47 都道府県)に対し scikit-learn の既定設定で実測したもの。乱数種 random_state=42 固定。バージョン差でごく小さな変動はあり得るが、「小データで MLP が CV 崩壊し線形・GBDT に劣る」という結論は頑健である。