論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
ニューラルネットワーク
Neural Network (NN)
脳のニューロンを模した数学モデル。複数層を重ねたものが深層学習。
機械学習NNNNニューラルネットMLP

🔖 キーワード索引 — 拡張版

ニューラルネットワークに関する用語を、 構造・学習・正則化・最適化 別に索引化します。

カテゴリキーワード(日本語)キーワード(英語)
構造入力層、 隠れ層、 出力層、 ニューロン、 重み、 バイアスinput/hidden/output layer, neuron, weight, bias
活性化関数ReLU、 シグモイド、 tanh、 Leaky ReLU、 GELU、 softmaxReLU, sigmoid, tanh, Leaky ReLU, GELU, softmax
学習誤差逆伝播、 勾配降下法、 損失関数、 学習率、 エポックbackpropagation, SGD, loss, learning rate, epoch
最適化Adam、 RMSprop、 モメンタム、 AdaGrad、 学習率スケジューリングAdam, RMSprop, momentum, AdaGrad, LR scheduling
正則化ドロップアウト、 L1/L2正則化、 早期終了、 バッチ正規化dropout, L1/L2, early stopping, batch norm
発展アーキCNN、 RNN、 LSTM、 GRU、 Transformer、 GNN、 オートエンコーダCNN, RNN, LSTM, GRU, Transformer, GNN, autoencoder

💡 30秒で分かる結論

🍰 まずはやさしく

脳の仕組みをまねた計算モデルです。

データからルールを見つけるために使います。

スマホの顔認証などで使われています。

まずはこの仕組みの基本を学びましょう。

📖 包括的解説 — この概念を完全マスター

📍 学習の3ステップ

  1. 定義を理解する:この概念は何か? 数式や条件を確認
  2. 具体例を見る:実データ(SSDSE 等)で計算してみる
  3. 応用する:自分のデータに適用、 結果を解釈

🔧 Python実装パターン

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import numpy as np

# --- 2層MLPの順伝播を手実装(合成データ・重みは固定値)---
def relu(z):    return np.maximum(0, z)      # 隠れ層の活性化
def sigmoid(z): return 1 / (1 + np.exp(-z))  # 出力層の活性化

x  = np.array([1.0, 2.0])                     # 入力(2次元)
W1 = np.array([[0.5, -0.2], [0.3, 0.8]])      # 隠れ層の重み(2ユニット×2入力)
b1 = np.array([0.1, -0.1])
W2 = np.array([0.6, -0.4])                    # 出力層の重み(1ユニット×2隠れ)
b2 = 0.2

h = relu(W1 @ x + b1)                          # 隠れ層: 線形変換 → ReLU
o = sigmoid(W2 @ h + b2)                        # 出力層: 線形変換 → sigmoid
print("hidden h =", h)
print("output o =", round(float(o), 3))

📚 統計概念マップでの位置

このページの上にある3つの概念マップ(関係マップ、 包含マップ、 ツリーマップ)でこの概念の位置づけが視覚的に分かります。 関連手法を辿って学習を進めましょう。

🎯 SSDSE-B-2026 で挑戦

統計データ活用コンペティションのSSDSE-B-2026データは、 47都道府県の社会経済データ。 この概念を使って以下のような分析ができます:

💡 よく使うコマンド集

機能 Python (pandas) Python (scipy)
要約統計df.describe()stats.describe()
平均df.mean()np.mean()
標準偏差df.std()np.std()
相関df.corr()stats.pearsonr()
t検定stats.ttest_ind()
回帰stats.linregress()
分布フィッティングstats.norm.fit()

🚧 一般的な落とし穴と対策

📊 結果報告の標準フォーマット

🌐 関連分野での応用

🎓 さらに学ぶための文献

🔗 統計用語ネットワーク

この概念は、 他の多くの統計概念と密接に関連しています。 ジャストインタイム型学習では、 必要に応じて関連用語へジャンプしながら全体像を構築します。

主要な関連概念のグループ

グループ 主要概念
記述統計平均、 中央値、 最頻値、 分散、 標準偏差、 共分散、 相関係数
可視化ヒストグラム、 散布図、 箱ひげ図、 ヒートマップ
推測統計標本平均、 標準誤差、 信頼区間、 p値、 有意水準
確率分布正規分布、 t分布、 χ²分布、 F分布、 二項分布
仮説検定t検定、 F検定、 χ²検定、 ノンパラ検定
回帰単回帰、 重回帰、 OLS、 Ridge、 LASSO
分類ロジスティック回帰、 決定木、 SVM、 k-NN
教師なし学習クラスタリング、 PCA、 因子分析
時系列ARIMA、 VAR、 指数平滑法、 自己相関
因果推論DiD、 IV、 傾向スコア、 交絡変数
前処理標準化、 正規化、 欠損値処理、 多重共線性対策
評価R²、 残差、 CV、 RMSE、 効果量

学習順序の推奨

  1. 記述統計(平均、 分散、 標準偏差)
  2. 可視化(ヒストグラム、 散布図)
  3. 確率分布(正規分布)
  4. 推測統計(標準誤差、 信頼区間、 p値)
  5. 仮説検定(t検定、 χ²検定)
  6. 相関と回帰(単回帰、 重回帰)
  7. 多変量解析(PCA、 クラスタリング)
  8. 機械学習(決定木、 RF、 NN)
  9. 時系列・因果推論(応用)

📝 実践練習 — SSDSE-B-2026 で挑戦

初級課題

  1. 東北6県の家計食料費の基本統計量を計算
  2. 食料費のヒストグラムを描く
  3. 食料費と教育費の散布図を描く
  4. 都道府県を「東日本/西日本」に分け、 平均を比較

中級課題

  1. 家計支出 5項目で相関行列を作成、 ヒートマップ可視化
  2. 食料費 → 教育費の単回帰を実行、 残差分析
  3. 家計5項目で PCA を実施、 バイプロット表示
  4. k-means (k=3) で都道府県をクラスタリング、 解釈

上級課題

  1. 地域別の家計パターンに有意差があるか ANOVA で検定
  2. 重回帰で教育費を予測、 多重共線性を VIF で確認
  3. Ridge/LASSO で正則化、 CV で α を最適化
  4. 階層クラスタリングと Ward 法で都道府県を分類、 デンドログラム作成

📍 あなたが今見ているもの

🍰 まずはやさしく

脳のネットワークのような数式です。

複雑なデータの分析に役立てます。

都道府県のデータ分析にも使えます。

定義から実装まで順番に解説します。

論文中に 「ニューラルネットワーク」として登場する用語。

ニューラルネットワーク とは:脳のニューロンを模した数学モデル。複数層を重ねたものが深層学習。

本ページでは「neural network」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。

「neural network」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。

🎨 多層パーセプトロンの世界観

🍰 まずはやさしく

層を重ねた計算の仕組みです。

複雑な形を近似(似せること)します。

部活の成績予測のような分析ができます。

計算に欠かせない部品について読みます。

🎨 多層パーセプトロンの世界観

ニューラルネットの最も基本的な形は 多層パーセプトロン (MLP)。 入力ベクトル $\mathbf{x}$ に対し、 各層が 線形変換 → 活性化関数 を繰り返す。 隠れ層を 1 つ持つだけでも、 十分なノード数があれば「任意の連続関数を任意精度で近似できる」(普遍近似定理、 Cybenko 1989, Hornik 1991)。 ただし「近似できる」と「学習できる」は別問題で、 実際には深い層・適切な活性化・正則化・大量データが鍵。

構成要素 役割 典型的選択 落とし穴
入力層特徴量ベクトル受け取り標準化・正規化スケール統一忘れ
隠れ層非線形特徴抽出Dense + ReLU勾配消失・発散
活性化関数非線形性導入ReLU / GELU / tanhdead neuron
出力層タスク特化回帰: 恒等、 分類: softmax不適切な活性化
損失関数学習目標MSE / cross-entropyタスクと損失のミスマッチ
最適化重み更新Adam / SGD+momentum学習率不適切
正則化過学習抑制Dropout / weight decay過剰正則化

ニューラルネットワークは、入力を何段階もの重み付き変換に通し、途中で非線形関数を挟むことで複雑な関係を近似するモデルです。表形式データでは、人口、所得、産業、医療、教育のような複数指標を同時に読み、最終層で分類や回帰の出力へ変換します。

非線形関係の密度
非線形な入力関係を層で表現する。
活性値の分布
活性値の分布で学習の偏りを見る。
外れ値と過学習
外れ値に過度適合していないか確認する。
  1. 入力層、隠れ層、出力層の役割を説明できる。
  2. 線形変換だけを重ねても表現力が増えない理由を説明できる。
  3. 活性化関数、損失関数、最適化手法を区別できる。
  4. 過学習を防ぐために、検証データ、正則化、早期終了を使う理由を説明できる。

📐 順伝播・逆伝播の数式

🍰 まずはやさしく

データの流れを数式にしたものです。

正解とのズレを計算して調整します。

買い物の傾向を数式で表すイメージです。

計算の手順と数式を確認しましょう。

L 層 MLP の順伝播(forward):

$$ \mathbf{h}^{(l)} = \sigma\left( W^{(l)} \mathbf{h}^{(l-1)} + \mathbf{b}^{(l)} \right),\quad l = 1, \dots, L $$

$\mathbf{h}^{(0)} = \mathbf{x}$, $\hat{\mathbf{y}} = \mathbf{h}^{(L)}$。 $\sigma$ は活性化関数(ReLU: $\max(0, z)$、 sigmoid: $1/(1+e^{-z})$、 tanh: $\tanh z$)。

損失(回帰の場合):

$$ \mathcal{L} = \frac{1}{n}\sum_{i=1}^n \left( y_i - \hat y_i \right)^2 $$

損失(多クラス分類):

$$ \mathcal{L} = -\frac{1}{n}\sum_{i=1}^n \sum_{c=1}^C y_{i,c} \log \hat p_{i,c},\quad \hat p_{i,c} = \mathrm{softmax}(\mathbf{h}^{(L)}_i)_c $$

逆伝播(連鎖律):

$$ \frac{\partial \mathcal{L}}{\partial W^{(l)}} = \delta^{(l)} \left(\mathbf{h}^{(l-1)}\right)^\top,\quad \delta^{(l)} = \left(W^{(l+1)}\right)^\top \delta^{(l+1)} \odot \sigma'\left(\mathbf{z}^{(l)}\right) $$

SGD/Adam の更新(学習率 $\eta$、 momentum $\beta$):

$$ W^{(l)} \leftarrow W^{(l)} - \eta \cdot \nabla_{W^{(l)}} \mathcal{L} $$

📐 NN の正則化手法

手法 目的 数式・実装
L2 正則化(weight decay)重みを小さく保つ$\mathcal{L} + \lambda \sum W^2$、 alpha= in sklearn
L1 正則化スパース解$\mathcal{L} + \lambda \sum |W|$
Dropoutアンサンブル近似学習時に p% ノード無効化
Early Stopping最適 epoch で停止early_stopping=True
Batch Normalization分布シフト抑制層出力を mini-batch で標準化
Data Augmentationデータ多様化画像回転・反転、 文章 swap
Mixup / CutMixサンプル合成$\tilde x = \lambda x_i + (1-\lambda) x_j$

🔬 数式を言葉で読み解く(順伝播・逆伝播)

$\mathbf{h}^{(l)} = \sigma(W^{(l)} \mathbf{h}^{(l-1)} + \mathbf{b}^{(l)})$ は「前層の出力を行列で混ぜ合わせ、 バイアスを足し、 非線形関数を通す」操作。 線形変換だけ重ねても結局 1 つの線形変換になってしまうので、 $\sigma$ の非線形性が「深さの効果」を生む。 ReLU は計算が軽く勾配が消えにくいため、 現代の標準。

逆伝播の式は「連鎖律を行列で書いたもの」。 $\delta^{(l)}$ は層 $l$ での「誤差信号」。 出力側から入力側に向かって順次伝播し、 各層の勾配 $\partial \mathcal{L} / \partial W^{(l)}$ を計算する。 計算量は順伝播とほぼ同じ(深層学習で実用化された理由)。

「数式を言葉で読み解く」と、 cross-entropy 損失は「正解クラスの予測確率の対数を最大化する」と等価。 softmax + cross-entropy の組合せでは、 出力層の勾配が単純に $\hat p - y$(予測 - 正解)になる驚くべき性質があり、 計算と理論の両面で扱いやすい。

SGD の更新 $W \leftarrow W - \eta \nabla \mathcal{L}$ は「損失の坂を下る方向に重みを少し動かす」操作。 $\eta$(学習率)が大きすぎると振動・発散、 小さすぎると遅い。 Adam は過去の勾配の二次モーメントを使って学習率を自動調整するため、 多くのタスクで安定して動く。

🔬 数式を言葉で読み解く(正則化とドロップアウト)

L2 正則化 $\mathcal{L} + \lambda \sum W^2$ は「重みが大きくなりすぎないようにペナルティをかける」。 これは Bayesian 的には「重みに正規事前分布 $W \sim \mathcal{N}(0, 1/\lambda)$ を置く MAP 推定」と等価。 $\lambda$ が大きいほど「重みを 0 に押し付ける力」が強い。

Dropout は学習時に各ノードを確率 $p$ で「無効化」(出力 0 にする)操作。 数式で書けば $\mathbf{h}' = \mathbf{h} \odot \mathbf{m}, \; m_j \sim \mathrm{Bernoulli}(1-p)$。 推論時は全ノード使うが出力を $(1-p)$ 倍する。 効果として「複数の小さなネットワークのアンサンブル」と解釈でき、 過学習を大幅に抑える。

「数式を言葉で読み解く」と、 Batch Normalization $\hat z = (z - \mu_B) / \sqrt{\sigma_B^2 + \epsilon}$ は「mini-batch 単位で出力を標準化」する操作。 これにより層間で活性化の分布が安定し、 大きい学習率が使えて収束が速くなる。 Layer Normalization は同じ発想を「サンプル内の特徴次元」で行うもので、 Transformer で多用される。

Early Stopping は「val loss が改善しなくなった時点で学習を止める」シンプルな正則化。 数式というよりプロトコルだが、 「最適 epoch でモデル容量を実質縮小」と解釈できる。 Dropout や L2 と直交する効果。

🧮 SSDSE-B を使ったニューラルネット回帰 — 実値計算例

SSDSE-B-2026 の都道府県データから「15歳未満人口(A1301)・65歳以上人口(A1303)・出生数(A4101)」を入力にして「総人口(A1101)」を予測するMLP(多層パーセプトロン)を構築します。

① ネットワーク構造

ユニット数活性化パラメータ数
入力30
隠れ132ReLU3×32+32 = 128
隠れ216ReLU32×16+16 = 528
出力1線形16×1+1 = 17
合計673

② 学習プロセス

1. 標準化(入力をz化)
2. 損失関数 = MSE(平均二乗誤差)
3. 最適化 = Adam(solver='adam')
4. 反復上限 = 2000、 目的変数も標準化
5. 早期終了・ドロップアウト等で過学習を抑制

③ 結果(下の実行例で測定)

検証 R² = 0.979(下の実測値)。 総人口は年齢別人口・出生数とほぼ線形の強い関係にあり、 線形活性化(identity)でも R²=0.933 と十分機能する。
ただしn=47は深層学習には少なすぎ、 過学習リスクが高い。 ドロップアウトと早期終了を併用すべき。 解釈性は線形回帰の方が圧倒的に上で、 状況に応じた使い分けが重要。

🧮 47 都道府県人口を MLP で予測

SSDSE-B-2026 を使って「15歳未満人口・65歳以上人口・出生数 → 総人口」を 3 層 MLP で予測。 sklearn の MLPRegressor は学習も評価も簡潔に書ける。

このコードでやること:47 都道府県の 3 特徴量から総人口を MLP (hidden=(32,16), ReLU, Adam) で回帰し、 train/test の R² と RMSE を確認する。

📥 入力データ (SSDSE-B-2026 抜粋):

都道府県 15歳未満人口 65歳以上人口 出生数 → 総人口(予測対象) 北海道 514,000 1,681,000 24,430 5,092,000 青森県 118,000 417,000 5,696 1,184,000 岩手県 120,000 407,000 5,432 1,163,000 ...
 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
import numpy as np
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.compose import TransformedTargetRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, root_mean_squared_error

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
year_col = df.columns[0]                       # 先頭列が年度
latest = df[df[year_col] == df[year_col].max()]  # 最新年の47都道府県
# 入力: 15歳未満人口(A1301)・65歳以上人口(A1303)・出生数(A4101)
X = latest[['A1301', 'A1303', 'A4101']].values.astype(float)
y = latest['A1101'].values.astype(float)       # 目的変数: 総人口(A1101)

X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0)
base = Pipeline([('scaler', StandardScaler()),
                 ('mlp', MLPRegressor(hidden_layer_sizes=(32, 16), activation='relu',
                                      solver='adam', max_iter=2000, random_state=0))])
# 目的変数もスケーリング(総人口は桁が大きく、未スケールだと学習が進まない)
model = TransformedTargetRegressor(regressor=base, transformer=StandardScaler())
model.fit(X_tr, y_tr)
mlp = model.regressor_.named_steps['mlp']
print(f'train R\N{SUPERSCRIPT TWO} = {r2_score(y_tr, model.predict(X_tr)):.3f}')
print(f'test  R\N{SUPERSCRIPT TWO} = {r2_score(y_te, model.predict(X_te)):.3f}')
print(f'test RMSE = {root_mean_squared_error(y_te, model.predict(X_te)):,.0f}')
print(f'最終 iter = {mlp.n_iter_},  loss = {mlp.loss_:.2e}')

📤 実行例:

train R² = 0.988 test R² = 0.979 test RMSE = 361,511 最終 iter = 173, loss = 6.02e-03

💬 train R²=0.988 / test R²=0.979 と高精度。 ただし n=47 とサンプルが極めて少ないため、 過学習リスクが高い。 線形回帰でもこの程度の精度が出ることに留意(NN を使う必然性は乏しい)。 むしろ NN は サンプル数が多く、 特徴量間の非線形相互作用が強い ときに本領を発揮する。

🧮 数式に値を入れて手で計算する: 単一ニューロンの出力

合成データで w·x + b と sigmoid 適用を計算する。

Step 1: 重みと入力

w = [0.5, -0.3, 0.8] x = [2, 4, 1] b = -1

Step 2: z = w·x + b

z = 0.5·2 + (-0.3)·4 + 0.8·1 + (-1) = 1 + (-1.2) + 0.8 + (-1) = -0.4

Step 3: a = σ(z)

σ(-0.4) = 1/(1+e^0.4) ≈ 0.401

🐍 Python で再現

1
2
3
4
5
6
7
8
import numpy as np
w = np.array([0.5, -0.3, 0.8])
x = np.array([2, 4, 1])
b = -1
z = w @ x + b
a = 1/(1 + np.exp(-z))
print(f"z = {z}")
print(f"a = {a:.3f}")

📤 実行結果

z = -0.4 a = 0.401

💬 手計算 (Step 3) 0.401 と Python 出力が完全一致。

🐍 Python 実装バリエーション — scikit-learn / Keras / PyTorch / JAX

① scikit-learn の MLPRegressor(最も手軽)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1301(15歳未満人口) A1303(65歳以上人口) A4101(出生数) 北海道 5,092,000 514,000 1,681,000 24,430 東京都 14,086,000 1,513,000 3,205,000 86,348 沖縄県 1,468,000 236,000 350,000 12,549 …(全 47 行)
 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
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.compose import TransformedTargetRegressor
from sklearn.model_selection import train_test_split

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df[df.columns[0]] == df[df.columns[0]].max()]
X = latest[['A1301', 'A1303', 'A4101']]   # 15歳未満・65歳以上・出生数
y = latest['A1101']                        # 総人口
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0)

base = Pipeline([
    ('scaler', StandardScaler()),
    ('mlp', MLPRegressor(hidden_layer_sizes=(16, 8), activation='relu',
                         max_iter=2000, random_state=0))
])
model = TransformedTargetRegressor(regressor=base, transformer=StandardScaler())
model.fit(X_tr, y_tr)
print('R\N{SUPERSCRIPT TWO}:', model.score(X_te, y_te))

② TensorFlow / Keras(柔軟な構築)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1301(15歳未満人口) A1303(65歳以上人口) A4101(出生数) 北海道 5,092,000 514,000 1,681,000 24,430 東京都 14,086,000 1,513,000 3,205,000 86,348 沖縄県 1,468,000 236,000 350,000 12,549 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import tensorflow as tf
from tensorflow.keras import layers, models, callbacks
import pandas as pd
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df[df.columns[0]] == df[df.columns[0]].max()]
X = StandardScaler().fit_transform(latest[['A1301', 'A1303', 'A4101']])
y = latest['A1101'].values.astype(float)

model = models.Sequential([
    layers.Input(shape=(3,)),
    layers.Dense(16, activation='relu'),
    layers.Dropout(0.2),
    layers.Dense(8, activation='relu'),
    layers.Dense(1)
])
model.compile(optimizer='adam', loss='mse', metrics=['mae'])

es = callbacks.EarlyStopping(patience=20, restore_best_weights=True)
hist = model.fit(X, y, validation_split=0.3, epochs=300, batch_size=8,
                 callbacks=[es], verbose=0)
print('最小val_loss:', min(hist.history['val_loss']))

③ PyTorch(研究・カスタム実装向け)

 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
import numpy as np
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset

torch.manual_seed(0)

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.Linear(8, 1)
        )
    def forward(self, x):
        return self.net(x)

model = MLP()
opt = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)
loss_fn = nn.MSELoss()

X_t = torch.tensor(np.asarray(X, dtype='float32'))   # DataFrame は ndarray に直してから渡す
y_t = torch.tensor(np.asarray(y, dtype='float32')).unsqueeze(1)
loader = DataLoader(TensorDataset(X_t, y_t), batch_size=8, shuffle=True)

for epoch in range(200):
    for xb, yb in loader:
        pred = model(xb); loss = loss_fn(pred, yb)
        opt.zero_grad(); loss.backward(); opt.step()

④ Keras Functional API(複雑なネットワーク)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from tensorflow.keras import layers, Model, Input

inp = Input(shape=(3,))
x = layers.Dense(16, activation='relu')(inp)
x = layers.BatchNormalization()(x)
x = layers.Dropout(0.2)(x)
x = layers.Dense(8, activation='relu')(x)
out = layers.Dense(1)(x)
model = Model(inp, out)
model.compile(optimizer='adam', loss='mse')

⑤ JAX/Flax(高速・関数型)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import jax, jax.numpy as jnp
from flax import linen as nn

class MLP(nn.Module):
    @nn.compact
    def __call__(self, x):
        x = nn.relu(nn.Dense(16)(x))
        x = nn.relu(nn.Dense(8)(x))
        return nn.Dense(1)(x)

model = MLP()
params = model.init(jax.random.PRNGKey(0), jnp.zeros((1, 3)))
# 以下に optax で最適化ループを書く

🐍 PyTorch で同じ NN を書いてみる

このコードでやること:sklearn の MLPRegressor と同等の 3 層 NN を PyTorch nn.Sequential で構築し、 学習ループを明示的に書く。 内部で何が起きているかを理解するため。

📥 入力データ: 上記と同じ 47 県の特徴量と目的変数。

 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
import torch
import torch.nn as nn
import numpy as np
from sklearn.metrics import r2_score
# X_tr, X_te, y_tr, y_te は上の calc セクションと同じ分割を使う

torch.manual_seed(0)
# 入力・出力とも標準化(NN は未スケールだと学習が不安定)
mu_x, sd_x = X_tr.mean(0), X_tr.std(0)
mu_y, sd_y = y_tr.mean(), y_tr.std()
xs_tr = (X_tr - mu_x) / sd_x
ys_tr = (y_tr - mu_y) / sd_y
xs_te = (X_te - mu_x) / sd_x
xt = torch.tensor(np.asarray(xs_tr, dtype='float32'))   # DataFrame は ndarray に直してから渡す
yt = torch.tensor(np.asarray(ys_tr, dtype='float32')).unsqueeze(1)

net = nn.Sequential(
    nn.Linear(3, 32), nn.ReLU(),
    nn.Linear(32, 16), nn.ReLU(),
    nn.Linear(16, 1),
)
opt = torch.optim.Adam(net.parameters(), lr=1e-2)
loss_fn = nn.MSELoss()

for epoch in range(2000):
    opt.zero_grad()                # 勾配リセット
    pred = net(xt)                 # 順伝播
    loss = loss_fn(pred, yt)       # 損失
    loss.backward()                # 逆伝播(連鎖律で全勾配を計算)
    opt.step()                     # Adam で重み更新
    if (epoch + 1) % 500 == 0:
        print(f'epoch {epoch+1}: loss = {loss.item():.4f}')

with torch.no_grad():
    pred_te = net(torch.tensor(np.asarray(xs_te, dtype='float32'))).numpy().ravel()
pred_te = pred_te * sd_y + mu_y    # 標準化を戻す
print(f'test R\N{SUPERSCRIPT TWO} = {r2_score(y_te, pred_te):.3f}')

📤 実行例:

epoch 500: loss = 0.0002 epoch 1000: loss = 0.0001 epoch 1500: loss = 0.0001 epoch 2000: loss = 0.0001 test R² = 0.996

💬 PyTorch では 順伝播 → 損失計算 → backward → optimizer.step の 4 ステップが明示。 loss.backward() が連鎖律で全パラメータの勾配を自動計算し、 opt.step() が SGD/Adam の更新を実行。 sklearn と結果がほぼ一致するのは、 数学的に同じ操作をしているから。

🐍 学習曲線で過学習を診断

このコードでやること:訓練損失と検証損失を epoch ごとに記録し、 過学習開始エポックを特定する。 Early Stopping の根拠データになる。

📥 入力データ: 47 県データ(train 32 / val 15 に分割)。

 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
import numpy as np
import pandas as pd
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df[df.columns[0]] == df[df.columns[0]].max()]
# 過学習を起こすため特徴量を6個・大きめの隠れ層(128,64)・正則化なし(alpha=0)
feats = ['A1301', 'A1303', 'A4101', 'B4101', 'A5101', 'A9101']
X = latest[feats].values.astype(float)
y = latest['A1101'].values.astype(float)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.4, random_state=1)
sx = StandardScaler().fit(X_tr)
sy = StandardScaler().fit(y_tr.reshape(-1, 1))

train_losses, val_losses = [], []
mlp = MLPRegressor(hidden_layer_sizes=(128, 64), max_iter=1,
                   warm_start=True, random_state=0, alpha=0.0)
for epoch in range(300):
    mlp.fit(sx.transform(X_tr), sy.transform(y_tr.reshape(-1, 1)).ravel())
    p_tr = sy.inverse_transform(mlp.predict(sx.transform(X_tr)).reshape(-1, 1)).ravel()
    p_te = sy.inverse_transform(mlp.predict(sx.transform(X_te)).reshape(-1, 1)).ravel()
    train_losses.append(mean_squared_error(y_tr, p_tr))
    val_losses.append(mean_squared_error(y_te, p_te))
best_epoch = int(np.argmin(val_losses)) + 1
print(f'最適 epoch = {best_epoch}')
print(f'最終 train MSE = {train_losses[-1]:.2e}')
print(f'最終 val   MSE = {val_losses[-1]:.2e}')
print(f'最小 val   MSE = {min(val_losses):.2e} at epoch {best_epoch}')

📤 実行例:

最適 epoch = 184 最終 train MSE = 4.75e+10 最終 val MSE = 4.47e+10 最小 val MSE = 4.30e+10 at epoch 184

💬 epoch 73 で val MSE が最小。 それ以降は train MSE は下がり続けるのに val MSE が上がる典型的な過学習。 early_stopping=True, validation_fraction=0.2 を設定すれば自動で止まる。 47 県という極小データでは過学習が起きやすく、 NN 不向き。

🐍 活性化関数の比較

このコードでやること:ReLU / tanh / sigmoid / GELU での学習曲線と最終性能を比較し、 ReLU 系が現代の主流である理由を実証する。

📥 入力データ: 47 県データ(標準化済み)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.compose import TransformedTargetRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
latest = df[df[df.columns[0]] == df[df.columns[0]].max()]
X = latest[['A1301', 'A1303', 'A4101']].values.astype(float)
y = latest['A1101'].values.astype(float)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0)

for act in ['relu', 'tanh', 'logistic', 'identity']:
    base = Pipeline([('scaler', StandardScaler()),
                     ('mlp', MLPRegressor(hidden_layer_sizes=(32, 16), activation=act,
                                          max_iter=2000, random_state=0))])
    model = TransformedTargetRegressor(regressor=base, transformer=StandardScaler())
    model.fit(X_tr, y_tr)
    r2 = r2_score(y_te, model.predict(X_te))
    print(f'{act:9s}: test R\N{SUPERSCRIPT TWO} = {r2:.3f}, iter = {model.regressor_.named_steps["mlp"].n_iter_}')

📤 実行例:

relu : test R² = 0.979, iter = 173 tanh : test R² = 0.960, iter = 184 logistic : test R² = 0.964, iter = 380 identity : test R² = 0.933, iter = 35

💬 ReLU が最高精度。 sigmoid (logistic) は勾配消失で収束遅延。 identity (恒等) は非線形性ゼロ = 線形回帰と同等で、 47 県のような線形関係が強いデータでは十分機能する。 これは「NN の必要性をデータが要求する場合」と「線形でも十分な場合」を見極める重要なシグナル。

⚠️ ニューラルネットワークの落とし穴 — 拡張版(実務で本当に困る5+件)

  1. 入力スケーリングを怠ると学習が爆発・収束しない:入力変数のスケールが大きく異なる(人口[百万]と失業率[%])と、 勾配が一部の重みだけ大きくなり学習が不安定になる。 必ず StandardScaler や MinMaxScaler で標準化/正規化する。 訓練データで fit、 テストには transform のみ適用するのが鉄則。 scikit-learn の Pipeline でラップすればミス防止になる。
  2. 過学習(overfitting)が止まらない:パラメータ数がサンプル数を超えるとほぼ確実に過学習する。 SSDSE のような n=47 で「隠れ層128ユニット」を入れると訓練誤差は0近くまで下がるが、 検証誤差は爆発する。 ドロップアウト(0.2〜0.5)、 L2正則化(weight_decay=1e−4等)、 早期終了、 そして そもそもネットワークを小さく することが対策。
  3. 勾配消失・勾配爆発(vanishing / exploding gradients):シグモイドや tanh を多層で重ねると勾配が指数的に小さくなり、 入力層付近の重みが更新されない。 ReLU 系(ReLU、 Leaky ReLU、 GELU)の利用、 He / Xavier 初期化、 バッチ正規化、 残差接続(ResNet)で緩和する。 RNN では LSTM や GRU で対応。
  4. 学習率の設定ミス:学習率が大きすぎると損失が振動・発散し、 小さすぎると学習が遅すぎる。 通常は 1e−3〜1e−4 から始める。 ReduceLROnPlateau や CosineAnnealing で動的に下げる。 LearningRateFinder(fast.ai)のような自動探索ツールも有用。 「動かないからとりあえずlr=0.01」は典型的な失敗。
  5. クラス不均衡を無視した分類:陽性事例が1%しかないデータで accuracy を見ても意味がない(全て陰性と予測しても99%)。 損失関数を class_weight で重み付け、 サンプリング(SMOTE等)、 評価指標を precision/recall/F1/AUC に変更するなど対処。 損失関数も BCE → Focal Loss など検討。
  6. シード固定なしでの再現性問題:torch.manual_seed、 np.random.seed、 random.seed、 torch.backends.cudnn.deterministic 全てを設定しないと結果が再現しない。 論文発表や業務報告では必須。 GPU では完全に決定論的にできないこともあるので注意。
  7. 解釈性の喪失:NN は「ブラックボックス」と呼ばれ、 なぜその予測をしたか説明が難しい。 SHAP、 LIME、 Integrated Gradients、 Permutation Importance などの XAI 手法を併用する。 法律や医療など説明責任のある分野では、 線形モデルや決定木の方が適切なこともある。

⚠️ ニューラルネットの落とし穴(深掘り)

  1. 勾配消失・発散 — sigmoid/tanh は深層で勾配が極小・極大になる。 ReLU で大幅改善されたが、 dead ReLU(ずっと 0 になるノード)の問題は残る。 Leaky ReLU, GELU, ELU 等で対処。
  2. 過学習 — 表現力が高いため小データでは即過学習。 Dropout, weight decay, early stopping, data augmentation で対処。 サンプル数が少なければ NN ではなく線形・木モデルを優先。
  3. 初期化の重要性 — Xavier (Glorot) / He 初期化は活性化の種類に応じた重み初期分散。 全 0 初期化は対称性が崩れず学習しない。 標準ガウス初期化も深層で発散しがち。
  4. 学習率の選択 — 大きすぎると振動・発散、 小さすぎると遅い・極小解に留まる。 cosine schedule, warmup, 1cycle policy など実証されたスケジューラを使う。
  5. batch size の影響 — 小さい batch は確率的・正則化効果あり、 大きい batch は安定だが汎化が悪化することも(large batch generalization gap)。 学習率と batch size は連動して調整。
  6. 標準化忘れ — 入力特徴量がスケール不均一だと勾配バランスが崩れ収束が遅い・解が悪い。 StandardScaler は必須。 出力も標準化すると学習が安定。
  7. 解釈性の困難 — 線形回帰や決定木と違い、 個々のニューロンの意味は不明。 SHAP, LIME, Integrated Gradients 等で部分的に解釈可能だが、 完全には程遠い。
  8. 再現性の壊れやすさ — random_state, cudnn deterministic, データ順序まで揃えないと結果が再現しない。 論文の追試・本番投入で大問題になりがち。
  9. ハイパーパラメータの組合せ爆発 — 層数・幅・活性化・最適化・学習率・正則化と多すぎる。 Optuna 等の Bayesian 最適化で自動化が現実解。

🗺️ 統計手法選択フローチャート

Q1: 何を知りたい?

Q2: データの種類は?

Q3: サンプルサイズは?

Q4: 仮定は?

📏 効果量の参照表

p値だけでなく効果量も併記するのが現代統計の標準。 主要な指標と Cohen の解釈基準:

統計量 効果量
2群平均差Cohen's d0.20.50.8
相関r0.10.30.5
線形回帰0.020.130.26
ANOVAη² (eta²)0.010.060.14
χ²Cramér's V0.10.30.5
ロジスティックOdds Ratio1.52.54.0

🗺️ 概念マップ — 3つの視点で体系を理解する

ニューラルネットワーク がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。

📍 体系階層のパス

🌐 統計・データサイエンス › 機械学習 › 教師あり学習 › ニューラルネットワーク

① 🔗 関係マップ — 「他の手法とどう繋がっているか」

中心に ニューラルネットワーク を置き、 そこから OLS・分類・重回帰・単回帰・Ridge回帰 計 5 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語あとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。

凡例:現在の用語上位カテゴリ兄弟(並列)前提発展形応用先2階層先

② ⭕ 包含マップ — 「どのカテゴリに含まれているか」

大きな円が小さな円を包含する Circle Packing 図。 「ニューラルネットワーク」は緑色でハイライト

📍現在地:統計・データサイエンス

③ 🌳 ツリーマップ — 「面積で見るボリューム比較」

長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「ニューラルネットワーク」は緑色でハイライト

🎯 3つのマップの使い分け

マップ 分かること こんな時に見る
🔗 関係マップ手法間の横の関係(前提→発展→応用)「次に何を学べばよい?」 学習順序の判断
⭕ 包含マップ分類体系の入れ子構造(上位⊃下位)「この手法はどんなジャンルに属する?」
🌳 ツリーマップ分野の規模比較(面積=ボリューム)「データサイエンス全体の俯瞰像」

💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは ニューラルネットワーク隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス教師あり学習 → ニューラルネットワーク という入れ子の位置を示します。 「教師あり学習には他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。

🔗 隣接手法への橋渡し

「ニューラルネットワーク」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。

ニューラルネットは「線形変換 + 非線形活性化」の合成。 SSDSE-B-2026 (47×16) は小規模すぎてニューラルネットの旨味が出にくく、 通常は線形回帰や勾配ブースティングが勝る。 NN は画像・音声・テキストのような大規模・高次元データで真価を発揮する。

🌳 手法選択フロー

「neural network」を含む手法選択は、 データの性質・分析目的・運用制約の 3 軸で決まる。 以下に典型シナリオと対応する手法の組合せを示す。

典型シナリオ別の手法選択

シナリオ重視する観点候補手法
データが大規模 (n が多い、 高次元)計算コスト / メモリ / 並列化が必要MLP (多層パーセプトロン)
外れ値が多い / ノイズあり頑健性 / 外れ値除去 / 中央値ベースCNN (畳み込みニューラルネット)
解釈性を重視する線形 / 木構造 / ルールベースRNN / LSTM / GRU
予測精度を最優先するアンサンブル / ハイパラ調整 / 交差検証Transformer
データが少ない正則化 / ベイズ / 転移学習 / 簡素なモデルGraph Neural Network (GNN)
リアルタイム/オンライン処理ストリーミング / 軽量モデル / 逐次更新Autoencoder / VAE

選んだ後の検証ステップ

  1. 前処理確認: 標準化 / 欠損処理 / カテゴリ変数のエンコード が適切か
  2. ハイパラ調整: 交差検証で安定する値を選ぶ
  3. 性能評価: タスクに応じた指標 (RMSE / Accuracy / F1 / AUC / シルエット等) を複数併用
  4. 頑健性チェック: 別手法 / 別シードで結果が大きく違わないか確認
  5. 解釈: 結果を分野知識と照らし合わせ、 意味のある発見になっているか検討

✅ 理解度チェック — 1 分で答える確認問題

ニューラルネットワークの基本概念が定着しているか、 以下の問いで確認しよう。

  1. 順伝播と逆伝播の役割: 順伝播では何が計算され、 逆伝播では何が更新されるか 1 行で説明せよ。
  2. 活性化関数の必要性: 活性化関数を入れずに線形変換だけを重ねた場合、 何が起きるか述べよ。
  3. SSDSE-B での適用例: 47 都道府県の年齢別人口(15歳未満・65歳以上)や出生数を説明変数、 総人口を目的変数とする回帰タスクで、 隠れ層を増やすと過学習が起こりやすい理由を 1-2 行で説明せよ。
  4. 勾配消失: シグモイド関数を深いネットワークで多用すると逆伝播時に何が起きるか、 また ReLU を採用すると何が改善されるか述べよ。
  5. 学習率: 学習率を大きくしすぎた場合と小さくしすぎた場合、 損失関数の挙動はそれぞれどうなるか 1 行ずつで答えよ。

解答例は他の用語ページ (誤差逆伝播 / 活性化関数 / 過学習) を読みながら自分で書き出してみよう。 概念マップ (上方) で関連用語の位置関係も再確認すると定着しやすい。

🎮 触って理解する

下のミニ・ニューラルネット(入力2 → 隠れ層3ニューロン → 出力1)で、 重み・バイアスのスライダーを動かすと、 各ニューロンの活性化と決定境界(2D平面の色塗り)がリアルタイムに更新されます。 左の平面をクリック/タップ(ドラッグも可)すると、 その点を入力 $(x_1, x_2)$ として順伝播した数値(隠れ層の活性値 $\mathbf{h}$ と出力 $o$)を表示します。 隠れ層の活性化関数(sigmoid / ReLU / tanh)も切り替えられます。 順伝播は $h_j=\sigma(W^{(1)}_j\!\cdot\!\mathbf{x}+b^{(1)}_j)$、 $o=\mathrm{sigmoid}(W^{(2)}\!\cdot\!\mathbf{h}+b^{(2)})$ を正確に計算しており、 すべてブラウザ内で完結・外部通信は一切ありません

決定境界(出力 o の値)
o=0 o=1
ネットワーク図
線の太さ=|重み|・赤=正/青=負・ノード色=活性値(選択点での順伝播)
重み・バイアスのスライダー(各 -3〜3)

$W^{(1)}_{j,i}$ は第 $j$ 隠れニューロンが受ける第 $i$ 入力の重み、 $b^{(1)}_j$ はそのバイアス、 $W^{(2)}_j$ は第 $j$ 隠れニューロンから出力への重み、 $b^{(2)}$ は出力のバイアスです。

🧭 直感 — なぜ「層の合成」で曲がった境界が作れるのか

1つの隠れニューロンは 重み付き和 → 活性化 により、 平面をおおよそ1本の(活性化で曲がった)境界で「半分に折る」働きをします。 隠れ層に複数ニューロンを置くと、 複数の折り目が重なり合い、 出力層がそれらを線形結合することで 直線1本では表せない非線形な決定境界が生まれます。 実際に上のスライダーで隠れニューロンの重みを大きくすると境界が鋭くなり、 符号を変えると折り目の向きが反転するのが見えます。 ここで活性化関数を sigmoid に固定したまま線形結合だけを重ねても、 合成はまた線形写像に戻ってしまう——だからこそ非線形な活性化関数が不可欠です(活性化を外すと、 どうスライダーを動かしても境界は必ず直線のままになります)。

🚧 触りながら気づく落とし穴

🚀 発展 — ここから先へ

🧠 直感をもう一段深める — 「重み付き和+活性化」を層で積む

1つのニューロン(パーセプトロン)は、 入力ベクトルに重みを掛けて足し合わせ(重み付き和 $z = \mathbf{w}\cdot\mathbf{x} + b$)、 そこに活性化関数 $\sigma$ を通すだけの素朴な部品です。 この部品を横に並べて「層」を作り、 層を縦に積む(前の層の出力を次の層の入力にする)と、 単純な部品の合成だけで複雑な入出力関係=関数を近似できます。 これが多層パーセプトロンの直感であり、 ニューラルネットワークの核です。

学習とは、 「望ましい出力との誤差」を測り、 その誤差を出力側から入力側へ連鎖律で逆算し(誤差逆伝播)、 各重みを誤差が減る向きに少しずつ動かすこと。 人が手で重みを調整する代わりに、 勾配降下法がこれを自動で行います。 上の🎮ウィジェットで手で決定境界を作った作業を、 アルゴリズムに丸ごと任せたものが「学習」だと考えると腑に落ちます。

「脳のニューロンの模倣」と言われますが、 これは発想の由来であって生物学的に正確な模型ではありません。 本質は「線形変換と非線形変換を交互に積むと表現力が跳ね上がる」という数学的事実です。 活性化 $\sigma$ を外して線形変換だけを積むと、 合成はまた1つの線形変換に戻り、 層を深くする意味が消えます(非線形性こそが「深さの効果」の源泉)。 ReLU が現代の標準活性化なのは、 計算が軽く勾配が消えにくいからです。

層を積むもう1つの意味は「特徴の階層」を自動で学ぶ点にあります。 浅い層は素朴な特徴(単純な組合せ)、 深い層はそれらを束ねた抽象的な特徴を表し、 人手の特徴設計を機械が肩代わりします。 これが深層学習が画像・音声・テキストで強い理由です。

⚠️ 落とし穴をもう一段深める — n=47 でNNはCVで崩れる(実測)

ニューラルネットは強力な反面、 失敗のしかたも多彩です。 特に重要な落とし穴を先に整理します。

これらの多くは n=47(SSDSE-B-2026 の47都道府県) のような小さな表形式データで一気に噴出します。 上の「実値計算例」では train/test 分割1回で test R²=0.979 と好成績でしたが、 それは1つの分割の運を含んだ値です。 5分割交差検証(CV)で honest に評価すると、 NNの脆さが見えてきます(以下はすべて SSDSE-B-2026・2023年・n=47 の実測値)。

モデル(同じ入力3特徴→総人口) CV R²(平均) std 最小fold コメント
線形回帰0.9870.0100.974スケール不変で頑健。 事実上の基準。
GBDT(勾配ブースティング)0.9500.0610.836木は前処理に鈍感で安定。
MLP(32,16) 目的変数を標準化0.9740.0250.942丁寧にやっても線形に並ぶだけで超えない。 分散は線形の約2.5倍。
MLP(32,16) 目的変数の標準化を忘れる−1.2800.834−2.882CVで崩壊。 平均を予測するより悪い(R²<0)。

💬 読み解き:(1) 総人口は年齢別人口・出生数とほぼ線形なので、 NNを丁寧にチューニングしても線形回帰に「並ぶ」のが精一杯で、 超えない。 しかも fold 間のばらつき(std=0.025)は線形(0.010)の約2.5倍で不安定。 (2) 決定的なのは最終行——目的変数(総人口=数百万の桁)の標準化を1つ忘れるだけで、 MLPは損失が桁の大きい方に引きずられて学習が進まず、 CV R²が負値(=平均を予測するより悪い)に崩壊する。 線形回帰・GBDTは目的変数のスケールに不変なので、 この失敗自体が起きない。

📥 再現コード(data/raw/SSDSE-B-2026.csv、 cp932、 skiprows=[1]、 最新年2023の47県):

 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
import pandas as pd, numpy as np, warnings
warnings.filterwarnings('ignore')
from sklearn.neural_network import MLPRegressor
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.compose import TransformedTargetRegressor
from sklearn.model_selection import cross_val_score, KFold

# 英字の項目コード(A1101 など)を使うので、2 行目の日本語名は読み飛ばす
nn_df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
yc = nn_df.columns[0]
latest = nn_df[nn_df[yc] == nn_df[yc].max()]   # 最新年 2023 の47都道府県 (n=47)
X = latest[['A1301', 'A1303', 'A4101']].values.astype(float)
y = latest['A1101'].values.astype(float)       # 総人口
cv = KFold(n_splits=5, shuffle=True, random_state=0)

def mlp(hidden, alpha, scale_y):
    # hidden_layer_sizes= は必ずキーワードで渡す(位置引数だと別の引数に入る)
    base = Pipeline([('s', StandardScaler()),
                     ('m', MLPRegressor(hidden_layer_sizes=hidden, activation='relu',
                                        solver='adam', max_iter=2000, alpha=alpha,
                                        random_state=0))])
    return TransformedTargetRegressor(base, transformer=StandardScaler()) if scale_y else base

models = {
    'Linear':             LinearRegression(),
    'GBDT':               GradientBoostingRegressor(random_state=0),
    'MLP(32,16) y標準化':   mlp((32, 16), 1e-4, True),
    'MLP(32,16) y未標準化':  mlp((32, 16), 1e-4, False),   # 前処理を1つ忘れるだけ
}
for name, m in models.items():
    s = cross_val_score(m, X, y, cv=cv, scoring='r2')
    note = '' if s.mean() > 0 else '  ← 平均を答えるより悪い(目的変数の標準化を忘れた結果)'
    print(f'{name:20s} CV 決定係数 {s.mean():+.3f} '
          f'(std {s.std():.3f}, 最悪 {s.min():+.3f}){note}')

📤 実行例:

Linear CV R2 = 0.987 (std 0.010, min 0.974) GBDT CV R2 = 0.950 (std 0.061, min 0.836) MLP(32,16) y標準化 CV R2 = 0.974 (std 0.025, min 0.942) MLP(32,16) y未標準化 CV R2 = -1.280 (std 0.834, min -2.882)

🎯 教訓:n=47 のような小・表形式データでは、 NNは(1)うまくいってもGBDT/線形を超えず、 (2)前処理の些細なミスで簡単に崩れる。 まず線形回帰やGBDTを基準にし、 NNは「大規模・高次元・強い非線形相互作用」があるときに使うのが定石です。

📝 補足(より正確な分析):ページ上部の実値計算例(train/test 1分割で test R²=0.979)は誤りではありませんが、 1つの分割の運を含みます。 少数データでは分割の当たり外れが大きいため、 単一分割の高スコアを過信せず、 上のように交差検証で fold 間のばらつき(std・最小fold)まで見ると、 モデルの脆さをより正確に評価できます。

🚀 発展をもう一段深める — 定理・最適化・アーキテクチャ