ニューラルネットワークに関する用語を、 構造・学習・正則化・最適化 別に索引化します。
| カテゴリ | キーワード(日本語) | キーワード(英語) |
|---|---|---|
| 構造 | 入力層、 隠れ層、 出力層、 ニューロン、 重み、 バイアス | input/hidden/output layer, neuron, weight, bias |
| 活性化関数 | ReLU、 シグモイド、 tanh、 Leaky ReLU、 GELU、 softmax | ReLU, 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 |
🍰 まずはやさしく
脳の仕組みをまねた計算モデルです。
データからルールを見つけるために使います。
スマホの顔認証などで使われています。
まずはこの仕組みの基本を学びましょう。
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データは、 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、 効果量 |
🍰 まずはやさしく
脳のネットワークのような数式です。
複雑なデータの分析に役立てます。
都道府県のデータ分析にも使えます。
定義から実装まで順番に解説します。
論文中に 「ニューラルネットワーク」として登場する用語。
ニューラルネットワーク とは:脳のニューロンを模した数学モデル。複数層を重ねたものが深層学習。
本ページでは「neural network」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。
「neural network」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。
🍰 まずはやさしく
層を重ねた計算の仕組みです。
複雑な形を近似(似せること)します。
部活の成績予測のような分析ができます。
計算に欠かせない部品について読みます。
ニューラルネットの最も基本的な形は 多層パーセプトロン (MLP)。 入力ベクトル $\mathbf{x}$ に対し、 各層が 線形変換 → 活性化関数 を繰り返す。 隠れ層を 1 つ持つだけでも、 十分なノード数があれば「任意の連続関数を任意精度で近似できる」(普遍近似定理、 Cybenko 1989, Hornik 1991)。 ただし「近似できる」と「学習できる」は別問題で、 実際には深い層・適切な活性化・正則化・大量データが鍵。
| 構成要素 | 役割 | 典型的選択 | 落とし穴 |
|---|---|---|---|
| 入力層 | 特徴量ベクトル受け取り | 標準化・正規化 | スケール統一忘れ |
| 隠れ層 | 非線形特徴抽出 | Dense + ReLU | 勾配消失・発散 |
| 活性化関数 | 非線形性導入 | ReLU / GELU / tanh | dead neuron |
| 出力層 | タスク特化 | 回帰: 恒等、 分類: softmax | 不適切な活性化 |
| 損失関数 | 学習目標 | MSE / cross-entropy | タスクと損失のミスマッチ |
| 最適化 | 重み更新 | Adam / SGD+momentum | 学習率不適切 |
| 正則化 | 過学習抑制 | Dropout / weight decay | 過剰正則化 |
ニューラルネットワークは、入力を何段階もの重み付き変換に通し、途中で非線形関数を挟むことで複雑な関係を近似するモデルです。表形式データでは、人口、所得、産業、医療、教育のような複数指標を同時に読み、最終層で分類や回帰の出力へ変換します。



🍰 まずはやさしく
データの流れを数式にしたものです。
正解とのズレを計算して調整します。
買い物の傾向を数式で表すイメージです。
計算の手順と数式を確認しましょう。
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} $$
| 手法 | 目的 | 数式・実装 |
|---|---|---|
| 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-2026 の都道府県データから「15歳未満人口(A1301)・65歳以上人口(A1303)・出生数(A4101)」を入力にして「総人口(A1101)」を予測するMLP(多層パーセプトロン)を構築します。
| 層 | ユニット数 | 活性化 | パラメータ数 |
|---|---|---|---|
| 入力 | 3 | — | 0 |
| 隠れ1 | 32 | ReLU | 3×32+32 = 128 |
| 隠れ2 | 16 | ReLU | 32×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は深層学習には少なすぎ、 過学習リスクが高い。 ドロップアウトと早期終了を併用すべき。 解釈性は線形回帰の方が圧倒的に上で、 状況に応じた使い分けが重要。
SSDSE-B-2026 を使って「15歳未満人口・65歳以上人口・出生数 → 総人口」を 3 層 MLP で予測。 sklearn の MLPRegressor は学習も評価も簡潔に書ける。
このコードでやること:47 都道府県の 3 特徴量から総人口を MLP (hidden=(32,16), ReLU, Adam) で回帰し、 train/test の R² と RMSE を確認する。
📥 入力データ (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 | 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 と高精度。 ただし n=47 とサンプルが極めて少ないため、 過学習リスクが高い。 線形回帰でもこの程度の精度が出ることに留意(NN を使う必然性は乏しい)。 むしろ NN は サンプル数が多く、 特徴量間の非線形相互作用が強い ときに本領を発揮する。
合成データで w·x + b と sigmoid 適用を計算する。
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}") |
💬 手計算 (Step 3) 0.401 と Python 出力が完全一致。
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)) |
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'])) |
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() |
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') |
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 で最適化ループを書く |
このコードでやること: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}') |
📤 実行例:
💬 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 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 が最高精度。 sigmoid (logistic) は勾配消失で収束遅延。 identity (恒等) は非線形性ゼロ = 線形回帰と同等で、 47 県のような線形関係が強いデータでは十分機能する。 これは「NN の必要性をデータが要求する場合」と「線形でも十分な場合」を見極める重要なシグナル。
p値だけでなく効果量も併記するのが現代統計の標準。 主要な指標と Cohen の解釈基準:
| 統計量 | 効果量 | 小 | 中 | 大 |
|---|---|---|---|---|
| 2群平均差 | Cohen's d | 0.2 | 0.5 | 0.8 |
| 相関 | r | 0.1 | 0.3 | 0.5 |
| 線形回帰 | R² | 0.02 | 0.13 | 0.26 |
| ANOVA | η² (eta²) | 0.01 | 0.06 | 0.14 |
| χ² | Cramér's V | 0.1 | 0.3 | 0.5 |
| ロジスティック | Odds Ratio | 1.5 | 2.5 | 4.0 |
ニューラルネットワーク がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。
🌐 統計・データサイエンス › 機械学習 › 教師あり学習 › ニューラルネットワーク
中心に ニューラルネットワーク を置き、 そこから OLS・分類・重回帰・単回帰・Ridge回帰 計 5 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語とあとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。
大きな円が小さな円を包含する Circle Packing 図。 「ニューラルネットワーク」は緑色でハイライト。
長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「ニューラルネットワーク」は緑色でハイライト。
| マップ | 分かること | こんな時に見る |
|---|---|---|
| 🔗 関係マップ | 手法間の横の関係(前提→発展→応用) | 「次に何を学べばよい?」 学習順序の判断 |
| ⭕ 包含マップ | 分類体系の入れ子構造(上位⊃下位) | 「この手法はどんなジャンルに属する?」 |
| 🌳 ツリーマップ | 分野の規模比較(面積=ボリューム) | 「データサイエンス全体の俯瞰像」 |
💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは ニューラルネットワーク の隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス → 教師あり学習 → ニューラルネットワーク という入れ子の位置を示します。 「教師あり学習には他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。
「ニューラルネットワーク」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。
ニューラルネットは「線形変換 + 非線形活性化」の合成。 SSDSE-B-2026 (47×16) は小規模すぎてニューラルネットの旨味が出にくく、 通常は線形回帰や勾配ブースティングが勝る。 NN は画像・音声・テキストのような大規模・高次元データで真価を発揮する。
「neural network」を含む手法選択は、 データの性質・分析目的・運用制約の 3 軸で決まる。 以下に典型シナリオと対応する手法の組合せを示す。
| シナリオ | 重視する観点 | 候補手法 |
|---|---|---|
| データが大規模 (n が多い、 高次元) | 計算コスト / メモリ / 並列化が必要 | MLP (多層パーセプトロン) 等 |
| 外れ値が多い / ノイズあり | 頑健性 / 外れ値除去 / 中央値ベース | CNN (畳み込みニューラルネット) 等 |
| 解釈性を重視する | 線形 / 木構造 / ルールベース | RNN / LSTM / GRU 等 |
| 予測精度を最優先する | アンサンブル / ハイパラ調整 / 交差検証 | Transformer 等 |
| データが少ない | 正則化 / ベイズ / 転移学習 / 簡素なモデル | Graph Neural Network (GNN) 等 |
| リアルタイム/オンライン処理 | ストリーミング / 軽量モデル / 逐次更新 | Autoencoder / VAE 等 |
ニューラルネットワークの基本概念が定着しているか、 以下の問いで確認しよう。
解答例は他の用語ページ (誤差逆伝播 / 活性化関数 / 過学習) を読みながら自分で書き出してみよう。 概念マップ (上方) で関連用語の位置関係も再確認すると定着しやすい。
下のミニ・ニューラルネット(入力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)})$ を正確に計算しており、 すべてブラウザ内で完結・外部通信は一切ありません。
$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(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.987 | 0.010 | 0.974 | スケール不変で頑健。 事実上の基準。 |
| GBDT(勾配ブースティング) | 0.950 | 0.061 | 0.836 | 木は前処理に鈍感で安定。 |
| MLP(32,16) 目的変数を標準化 | 0.974 | 0.025 | 0.942 | 丁寧にやっても線形に並ぶだけで超えない。 分散は線形の約2.5倍。 |
| MLP(32,16) 目的変数の標準化を忘れる | −1.280 | 0.834 | −2.882 | CVで崩壊。 平均を予測するより悪い(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}') |
📤 実行例:
🎯 教訓:n=47 のような小・表形式データでは、 NNは(1)うまくいってもGBDT/線形を超えず、 (2)前処理の些細なミスで簡単に崩れる。 まず線形回帰やGBDTを基準にし、 NNは「大規模・高次元・強い非線形相互作用」があるときに使うのが定石です。
📝 補足(より正確な分析):ページ上部の実値計算例(train/test 1分割で test R²=0.979)は誤りではありませんが、 1つの分割の運を含みます。 少数データでは分割の当たり外れが大きいため、 単一分割の高スコアを過信せず、 上のように交差検証で fold 間のばらつき(std・最小fold)まで見ると、 モデルの脆さをより正確に評価できます。