本ページは、 ニューラルネットワーク (NN) の基礎概念を統合的に解説します。 パーセプトロンから始まり、 多層パーセプトロン (MLP)、 活性化関数、 誤差逆伝播、 最適化器まで一気通貫で扱います。
近年の深層学習・LLM はすべてここから派生しています。 まずは「線形変換と非線形活性化を交互に重ねる」という基本構造を、 数式と Python で押さえます。
論文記事から各用語のリンクをクリックすると、 該当箇所が開きます:
| 章 | 内容 |
|---|---|
| 1. パーセプトロン | NN の最小単位 |
| 2. 多層化(MLP) | 隠れ層を持つ NN |
| 3. 活性化関数 | 非線形性の源 |
| 4. 損失関数 | 予測の良し悪しの尺度 |
| 5. 誤差逆伝播 | 勾配の計算 |
| 6. 最適化器 | SGD・Adam 等 |
| 7. 初期化と正規化 | 学習の安定化 |
| 8. 学習ループ | PyTorch 実装例 |
入力 $\mathbf{x} = (x_1, \dots, x_p)^\top$、 重み $\mathbf{w}$、 バイアス $b$、 活性化 $\phi$ に対し:
$$z = \mathbf{w}^\top \mathbf{x} + b, \qquad a = \phi(z)$$
記号読み:$z$ は「ゼット」プリアクティベーション(活性化前の値)、 $a$ は「エー」アクティベーション(活性化後)。 $\phi$ は「ファイ」活性化関数。
$\phi$ がステップ関数のとき。 線形分離可能なデータのみ完璧分類。 XOR が解けない(線形分離不能)ことが指摘され、 第一次 AI 冬の時代を招いた。
$\mathbf{x}=(1,2)$、 $\mathbf{w}=(0.5,-0.3)$、 $b=0.1$、 $\phi=$ ReLU のとき:
1953 年代後半に提案、 1986 年の Rumelhart の誤差逆伝播で実用化。 隠れ層を持つことで XOR も解ける。
$L$ 層 MLP において、 各層 $\ell$ で:
$$\mathbf{z}^{(\ell)} = \mathbf{W}^{(\ell)} \mathbf{a}^{(\ell-1)} + \mathbf{b}^{(\ell)}, \qquad \mathbf{a}^{(\ell)} = \phi^{(\ell)}(\mathbf{z}^{(\ell)})$$
記号読み:$\mathbf{W}^{(\ell)}$ は「ダブリュー上付きエル」、 第 $\ell$ 層の重み行列。 入力 $\mathbf{a}^{(0)} = \mathbf{x}$、 出力 $\hat{y} = \mathbf{a}^{(L)}$。
万能近似定理(Cybenko 1989、 Hornik 1991):1層の隠れ層を持つ MLP は十分な幅があれば任意の連続関数を任意の精度で近似できる。 ただし幅は指数的に大きくなりうるため、 実用では深くする。
NN に非線形性を入れる装置。 線形ばかりだと層を重ねても線形のまま。
$$\sigma(z) = \frac{1}{1+e^{-z}}, \quad \sigma'(z) = \sigma(z)(1-\sigma(z))$$
$$\tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}}$$
出力 (-1, 1)。 ゼロ中心で sigmoid より好まれる場合あり。
$$\mathrm{ReLU}(z) = \max(0, z)$$
$$\mathrm{softmax}(z)_k = \frac{e^{z_k}}{\sum_{j=1}^{K} e^{z_j}}$$
多クラス分類の出力層で、 ロジット $z_k$ を確率に変換。
| タスク | 損失関数 | 出力層 |
|---|---|---|
| 回帰 | MSE / MAE / Huber | 恒等関数 |
| 二値分類 | バイナリ交差エントロピー | Sigmoid |
| 多クラス分類 | 交差エントロピー | Softmax |
| 多ラベル | BCE × クラス数 | クラス別Sigmoid |
$$L_{\text{CE}} = -\sum_{k=1}^{K} y_k \log \hat{p}_k$$
順伝播で予測 → 損失計算 → 勾配を出力側から入力側に向かって計算する。
$$\frac{\partial L}{\partial W^{(\ell)}} = \frac{\partial L}{\partial \mathbf{a}^{(L)}} \cdot \frac{\partial \mathbf{a}^{(L)}}{\partial \mathbf{z}^{(L)}} \cdots \frac{\partial \mathbf{a}^{(\ell)}}{\partial \mathbf{z}^{(\ell)}} \cdot \frac{\partial \mathbf{z}^{(\ell)}}{\partial W^{(\ell)}}$$
各層の局所勾配を計算し、 連鎖律で合成する。 計算複雑度は順伝播と同じ $O(\text{パラメータ数})$。
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.pipeline import Pipeline from sklearn.compose import TransformedTargetRegressor from sklearn.model_selection import cross_val_score, KFold df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df['SSDSE-B-2026'] = pd.to_numeric(df['SSDSE-B-2026'], errors='coerce') df = df[df['SSDSE-B-2026'] == 2023] X = df[['A1101', 'A4101', 'L3221']].values y = df['I5102'].values pipe = Pipeline([ ('scale', StandardScaler()), ('mlp', MLPRegressor(hidden_layer_sizes=(16, 8), activation='relu', solver='adam', learning_rate_init=0.001, max_iter=2000, early_stopping=True, random_state=42)) ]) model = TransformedTargetRegressor(regressor=pipe, transformer=StandardScaler()) scores = cross_val_score(model, X, y, cv=KFold(5, shuffle=True, random_state=42), scoring='r2') print(f'R² (5-fold): {scores.mean():.3f} ± {scores.std():.3f}') |
💬 読み方:検証 15 県の val_loss が 20 epoch 続けて最小値を更新しなかったため、上限 200 epoch のうち epoch 80 (0 始まり) で打ち切られた。つまり最良の val_loss は epoch 60 前後で出ている。このコードは打ち切り時点の重みをそのまま残し、最良時点の重みには戻さないので、使うなら best_val_loss 更新時に state_dict を保存しておく。clip_grad_norm_ は勾配の大きさを 1.0 以下に抑え、Dropout・勾配クリップ・早期終了の 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # ── 上のブロックと同じ題材(総人口・出生数・消費支出 → 一般診療所数)で # 学習用ローダと検証データを用意する ── _d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _d = _d[_d['SSDSE-B-2026'] == 2023] _X = StandardScaler().fit_transform(_d[['A1101', 'A4101', 'L3221']].astype(float)) _y = _d['I5102'].astype(float).values.reshape(-1, 1) _ys = StandardScaler().fit_transform(_y) X_tr, X_val, y_tr, y_val = train_test_split(_X, _ys, test_size=0.3, random_state=42) import torch from torch.utils.data import DataLoader, TensorDataset train_loader = DataLoader( TensorDataset(torch.tensor(X_tr, dtype=torch.float32), torch.tensor(y_tr, dtype=torch.float32)), batch_size=8, shuffle=True) X_val = torch.tensor(X_val, dtype=torch.float32) y_val = torch.tensor(y_val, dtype=torch.float32) import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset ## 再現性のための seed 固定 torch.manual_seed(42) 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() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) criterion = nn.MSELoss() ## 学習ループ + Early Stopping best_val_loss = float('inf') patience = 20 counter = 0 for epoch in range(200): model.train() for xb, yb in train_loader: pred = model(xb) loss = criterion(pred, yb) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) ## 勾配クリッピング optimizer.step() ## Early Stopping 判定 model.eval() with torch.no_grad(): val_loss = criterion(model(X_val), y_val).item() if val_loss < best_val_loss: best_val_loss = val_loss counter = 0 else: counter += 1 if counter >= patience: print(f'Early stopping at epoch {epoch}') break |
💬 読み方:train と val の loss 曲線が乖離していけば過学習。 ReduceLROnPlateau は val 改善停止時に lr を半減し、 EarlyStopping は patience 経過後に学習を打ち切る。 history で学習進捗を後から再確認可能。
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 39 | import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # ── 上のブロックと同じ題材(総人口・出生数・消費支出 → 一般診療所数)で # 学習用ローダと検証データを用意する ── _d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _d = _d[_d['SSDSE-B-2026'] == 2023] _X = StandardScaler().fit_transform(_d[['A1101', 'A4101', 'L3221']].astype(float)) _y = _d['I5102'].astype(float).values.reshape(-1, 1) _ys = StandardScaler().fit_transform(_y) X_tr, X_val, y_tr, y_val = train_test_split(_X, _ys, test_size=0.3, random_state=42) from tensorflow import keras from tensorflow.keras import layers, callbacks model = keras.Sequential([ layers.Input(shape=(3,)), layers.BatchNormalization(), layers.Dense(16, activation='relu', kernel_initializer='he_normal'), layers.Dropout(0.2), layers.Dense(8, activation='relu', kernel_initializer='he_normal'), layers.Dense(1) ]) model.compile(optimizer=keras.optimizers.Adam(1e-3), loss='mse', metrics=['mae']) es = callbacks.EarlyStopping(monitor='val_loss', patience=20, restore_best_weights=True) lr_sched = callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=10) history = model.fit(X_tr, y_tr, validation_split=0.2, epochs=200, batch_size=8, callbacks=[es, lr_sched], verbose=0) ## 学習曲線のプロット import matplotlib.pyplot as plt plt.plot(history.history['loss'], label='train') plt.plot(history.history['val_loss'], label='val') plt.xlabel('Epoch'); plt.ylabel('MSE Loss'); plt.legend() |
SSDSE-B-2026 から「総人口・65 歳以上人口・消費支出・出生数」の 4 指標を入力に、 「一般診療所数」を当てるタスクを考えます。 線形回帰は「重みの 1 回掛け算 + 足し算」一段ですが、 ニューラルネットは「重み付け → 非線形変換 → また重み付け → 非線形変換 → 最後に出力」と何段も積み木を重ねます。 各層の非線形(ReLU や tanh)が「曲がり」を作り、 直線では表現できない複雑なパターンを捉えられる可能性があります(ただし後述の通り、 47 件の小標本ではまず線形回帰と比較することが重要です)。
$$ h^{(l)} = \sigma\!\left( W^{(l)} h^{(l-1)} + b^{(l)} \right), \quad l = 1, 2, \ldots, L $$
$$ \hat{y} = W^{(L+1)} h^{(L)} + b^{(L+1)} $$
ここで $h^{(0)} = x$(入力ベクトル: 標準化された 47 都道府県指標)、 $\sigma$ は活性化関数(ReLU など)、 $W^{(l)}, b^{(l)}$ が層 $l$ の学習パラメータ。 損失 $\mathcal{L} = \frac{1}{n}\sum_i (y_i - \hat{y}_i)^2$ を勾配降下で最小化します。
| 記号 | 意味 | SSDSE-B-2026 での具体例 |
|---|---|---|
$x$ | 入力ベクトル | 標準化済みの 47 都道府県指標 (A1101 総人口, A4101 出生数, L3221 消費支出…) |
$W^{(l)}$ | 層 $l$ の重み行列 | 学習で更新する パラメータ。 1 層目は (16, 3) 形状 |
$\sigma$ | 活性化関数 | ReLU、 tanh、 sigmoid、 GELU など。 非線形性の源泉 |
$\hat{y}$ | 予測値 | I5102 一般診療所数(回帰)または合計特殊出生率 A4103 の予測 |
$\mathcal{L}$ | 損失関数 | MSE = $\frac{1}{47}\sum(y_i-\hat{y}_i)^2$ |
3 都道府県だけ取り出して、 標準化済み入力 $x$ から 1 層目の中間出力 $h^{(1)}$ までを手計算してみます(重み $W^{(1)}$ は学習後の代表値を仮定)。
| 都道府県 | $x_1$ 総人口 | $x_2$ 出生数 | $x_3$ 消費支出 | $z = W \cdot x + b$ | $h = \text{ReLU}(z)$ |
|---|---|---|---|---|---|
| 東京都 | +4.13 | +4.18 | +1.90 | +2.96 | +2.96 |
| 北海道 | +0.88 | +0.53 | +0.04 | +0.37 | +0.37 |
| 沖縄県 | -0.43 | -0.17 | -1.87 | -0.87 | 0.00 |
ReLU で 負値は 0 にクリップ されるため、 沖縄県は 1 層目で「死んだニューロン」状態。 並列の別ニューロンが沖縄パターンを拾うように冗長設計するのがポイント。
1 2 3 4 5 6 7 8 9 | import numpy as np X = np.array([[4.13, 4.18, 1.90], [0.88, 0.53, 0.04], [-0.43,-0.17,-1.87]]) W = np.array([[0.4, 0.2, 0.3]]) b = np.array([-0.1]) z = X @ W.T + b h = np.maximum(0, z) # ReLU print(z.round(2)); print(h.round(2)) |
ここからはニューラルネットを「概念図」から「動くコード」まで一直線に追いかける拡張補講です。 SSDSE-B-2026 が提供する 47 都道府県の公的統計を題材に、 散布図で関係を見て、 ヒストグラムで分布を確認し、 多群箱ひげ図でクラスタ間の差を切り出し、 そのうえで隠れ層 2 段の多層パーセプトロン (MLP) を学習させます。 すべての数値は架空ではなく e-Stat 由来の公的データ であり、 合成乱数は用いません。 各コードブロックは このコードでやること → 入力データ → 実コード → 実行例 → 読み方 の 4 要素パターンに従います。
入力ベクトルは 4 次元、 出力(教師信号)は「一般診療所数 I5102」とします。 SSDSE-B-2026 のコードと意味を表で整理しておきます(出典: 独立行政法人統計センター 教育用標準データセット SSDSE-B-2026、 47 都道府県・2023 年度データ)。
| 変数 | SSDSE コード | 意味 | 単位 | 役割 |
|---|---|---|---|---|
| x1 | A1101 | 総人口 | 人 | スケール指標 |
| x2 | A1303 | 65 歳以上人口 | 人 | 高齢化指標 |
| x3 | L3221 | 消費支出(二人以上の世帯) | 円/月 | 経済指標 |
| x4 | A4101 | 出生数 | 人 | 人口動態指標 |
| y | I5102 | 一般診療所数 | 所 | 目的変数 |
この 4 変数は 互いに強い相関 を持ちます(総人口と一般診療所数の相関は r ≈ 0.972)。 相関が強い入力では線形回帰でも高い精度が出るため、 「NN が本当に必要か」をベースライン比較で確かめる題材として最適です。 隠れ層 + 非線形活性化が効くのは、 スケール差の大きい入力や、 線形では拾えない残差構造がある場合です。
| 都道府県 | x1 総人口 (人) | x2 65 歳以上人口 (人) | x3 消費支出 (円/月) | x4 出生数 (人) | y 一般診療所数 (所) |
|---|---|---|---|---|---|
| 北海道 | 5,092,000 | 1,681,000 | 296,888 | 24,430 | 3,403 |
| 東京都 | 14,086,000 | 3,205,000 | 341,320 | 86,348 | 14,894 |
| 大阪府 | 8,763,000 | 2,424,000 | 271,246 | 55,292 | 8,877 |
| 愛知県 | 7,477,000 | 1,923,000 | 300,221 | 48,402 | 5,682 |
| 福岡県 | 5,103,000 | 1,452,000 | 309,000 | 33,942 | 4,806 |
| 沖縄県 | 1,468,000 | 350,000 | 251,222 | 12,549 | 928 |
| 鳥取県 | 537,000 | 179,000 | 272,599 | 3,263 | 474 |
| 秋田県 | 914,000 | 357,000 | 272,086 | 3,611 | 806 |
東京都は 総人口(14,086,000 人)・一般診療所数(14,894 所)ともに突出 し、 鳥取県は 最小(537,000 人・474 所)。 規模の階層構造が強い右裾分布を作っており、 標準化なしでは NN の勾配が大規模県に支配されます。 ここが前処理設計の出発点です。
まず散布図で「総人口 × 一般診療所数」の関係を視覚化します。 相関係数は $r \approx 0.972$(決定係数 $R^2 \approx 0.94$)とほぼ直線的な強い関係で、 この 2 変数を両方入力に使うと情報が重複する(多重共線性)ことも読み取れます。
入力変数の分布が「左右対称ベル型」か「右に裾を引く」かは、 標準化・対数変換の要否や損失関数(MSE か Huber か)の選択に直結します。
KMeans クラスタリングで 47 都道府県をグループ化し、 クラスタ別に一般診療所数の中央値・四分位範囲・外れ値を箱ひげで比較します。 これは「クラスタ(地域構造)の情報を NN に与えるべきか?」の判断材料です。
describe() で分布スケールを把握します。 標準化前にスケール差を眼で確認するのが目的です。1 2 3 4 5 6 7 8 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df['SSDSE-B-2026'] = pd.to_numeric(df['SSDSE-B-2026'], errors='coerce') df = df[df['SSDSE-B-2026'] == 2023] cols = {'A1101':'pop', 'A1303':'aged', 'L3221':'cons', 'A4101':'births', 'I5102':'clinic'} df = df.rename(columns=cols)[['Prefecture'] + list(cols.values())] print(df[['pop','aged','cons','births','clinic']].describe().round(2)) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split X = df[['pop','aged','cons','births']].values y_raw = df['clinic'].values.reshape(-1, 1) scaler = StandardScaler() Xs = scaler.fit_transform(X) y = StandardScaler().fit_transform(y_raw) # 目的変数も標準化 X_train, X_test, y_train, y_test = train_test_split( Xs, y, test_size=12, random_state=42) print('X_train.shape =', X_train.shape, ' y_train.shape =', y_train.shape) print('X_test.shape =', X_test.shape, ' y_test.shape =', y_test.shape) print('Xs mean (col):', Xs.mean(axis=0).round(3)) print('Xs std (col):', Xs.std(axis=0).round(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 26 27 28 29 30 31 32 33 34 35 36 37 | import numpy as np def init_weights(shapes): rng = np.random.default_rng(0) # 再現性のため seed 固定 return [rng.normal(0, np.sqrt(2 / s[0]), size=s) for s in shapes] # He 初期化 W1, W2, W3 = init_weights([(4, 16), (16, 8), (8, 1)]) b1 = np.zeros((1, 16)); b2 = np.zeros((1, 8)); b3 = np.zeros((1, 1)) + y_train.mean() lr = 0.02 loss_hist = [] for epoch in range(400): z1 = X_train @ W1 + b1; h1 = np.maximum(0, z1) z2 = h1 @ W2 + b2; h2 = np.maximum(0, z2) y_hat = h2 @ W3 + b3 loss = ((y_hat - y_train) ** 2).mean() loss_hist.append(loss) dL = 2 * (y_hat - y_train) / X_train.shape[0] dW3 = h2.T @ dL; db3 = dL.sum(axis=0, keepdims=True) dh2 = dL @ W3.T; dz2 = dh2 * (z2 > 0) dW2 = h1.T @ dz2; db2 = dz2.sum(axis=0, keepdims=True) dh1 = dz2 @ W2.T; dz1 = dh1 * (z1 > 0) dW1 = X_train.T @ dz1; db1 = dz1.sum(axis=0, keepdims=True) W1 -= lr * dW1; b1 -= lr * db1 W2 -= lr * dW2; b2 -= lr * db2 W3 -= lr * dW3; b3 -= lr * db3 if epoch % 100 == 0: print(f'epoch={epoch:3d} train MSE={loss:.4f}') # 学習後の重みで訓練・テストの MSE を測る def predict(X): h1 = np.maximum(0, X @ W1 + b1) h2 = np.maximum(0, h1 @ W2 + b2) return h2 @ W3 + b3 tr_mse = ((predict(X_train) - y_train) ** 2).mean() te_mse = ((predict(X_test) - y_test) ** 2).mean() print(f'final train MSE={tr_mse:.4f} test MSE={te_mse:.4f}') |
MLPRegressor で 1 行 SGD 学習し、 学習曲線を比較します。 実務ではこちらを使います。1 2 3 4 5 6 7 8 9 10 11 12 13 | from sklearn.neural_network import MLPRegressor from sklearn.metrics import mean_squared_error, r2_score mlp = MLPRegressor(hidden_layer_sizes=(16, 8), activation='relu', solver='adam', learning_rate_init=0.02, max_iter=400, random_state=42) mlp.fit(X_train, y_train.ravel()) y_pred_tr = mlp.predict(X_train) y_pred_te = mlp.predict(X_test) print('train MSE =', round(mean_squared_error(y_train, y_pred_tr), 4)) print('test MSE =', round(mean_squared_error(y_test, y_pred_te), 4)) print('train R2 =', round(r2_score(y_train, y_pred_tr), 3)) print('test R2 =', round(r2_score(y_test, y_pred_te), 3)) |
nn.Module で同じネット構成を書き、 自動微分で 1 epoch の勾配を計算します。 「自分で逆伝播を書かなくていい」のがフレームワーク利用の最大の利点。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 torch import torch.nn as nn torch.manual_seed(0) # 再現性のため seed 固定 class ClinicNet(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(4, 16) self.fc2 = nn.Linear(16, 8) self.fc3 = nn.Linear(8, 1) self.act = nn.ReLU() def forward(self, x): h = self.act(self.fc1(x)) h = self.act(self.fc2(h)) return self.fc3(h) net = ClinicNet() opt = torch.optim.Adam(net.parameters(), lr=0.02) loss_fn = nn.MSELoss() Xt = torch.tensor(X_train, dtype=torch.float32) yt = torch.tensor(y_train, dtype=torch.float32) y_hat = net(Xt); loss = loss_fn(y_hat, yt) loss.backward() print('initial loss =', round(loss.item(), 4)) print('fc1.weight.grad mean =', round(net.fc1.weight.grad.abs().mean().item(), 4)) opt.step(); opt.zero_grad() |
| モデル | 隠れ層 | パラメータ数 | train MSE | test MSE | test R² |
|---|---|---|---|---|---|
| 線形回帰 | 0 | 5 | 0.0080 | 0.3903 | 0.84 |
| MLP 4-8-1 | 1 段 | 49 | 0.0061 | 0.4116 | 0.84 |
| MLP 4-16-8-1 | 2 段 | 225 | 0.0069 | 0.3979 | 0.84 |
| MLP 4-32-16-1 | 2 段(広い) | 705 | 0.0044 | 0.5979 | 0.76 |
| MLP 4-16-16-16-1 | 3 段 | 641 | 0.0049 | 0.5944 | 0.76 |
47 都道府県データでは 線形回帰と小さな MLP がテスト R² 0.84 で並ぶ。 これ以上深くしたり広げたりすると訓練誤差は下がるが、 過学習でテスト性能が落ちる(R² 0.76)。 「ネットを大きくすれば賢くなる」は 大規模データの世界での話。
| 活性化 | 式 | 勾配範囲 | 長所 | 短所 |
|---|---|---|---|---|
| sigmoid | 1/(1+e-x) | (0, 0.25] | 確率解釈 | 勾配消失 |
| tanh | (ex-e-x)/(ex+e-x) | (0, 1] | ゼロ中心 | 深層では消失 |
| ReLU | max(0, x) | {0, 1} | 計算軽量・勾配消失なし | 死亡ニューロン |
| Leaky ReLU | max(0.01x, x) | {0.01, 1} | 死亡防止 | 傾き選択が任意 |
| GELU | x·Φ(x) | 滑らか・正値中心 | Transformer の標準 | 計算重め |
| SiLU/Swish | x·sigmoid(x) | 滑らか・小負値も保持 | 大規模で安定 | CPU でやや重い |
| タスク | 代表的損失 | 出力層 | SSDSE-B-2026 での例 |
|---|---|---|---|
| 回帰 | MSE | 恒等関数 (linear) | 一般診療所数 (I5102) / 合計特殊出生率 (A4103) の予測 |
| 回帰(外れ値耐性) | Huber / smooth L1 | 恒等関数 | 東京や沖縄など外れ気味の県を含む予測 |
| 2 クラス分類 | BCE | sigmoid | 「出生率 1.3 超か否か」二値判定 |
| 多クラス分類 | 交差エントロピー | softmax | 「8 地域ブロックのどれか」分類 |
| 順序ロジット | cumulative link | 複数 sigmoid | 「高齢化区分(4 階級)」予測 |
| 不確実性付き回帰 | 負の対数尤度 | 平均 + 分散の 2 出力 | 出生率の予測区間も同時に出力 |
| 手法 | 仕組み | SSDSE-B-2026 47 件への効果 |
|---|---|---|
| L2 重み減衰 | 重みの 2 乗和を罰則項 | 小データでは強めに効かせる (α=0.01〜0.1) |
| Dropout | 学習時にノードをランダム遮断 | 47 件では p=0.1〜0.2、 大きすぎると消える |
| 早期終了 | 検証損失が下げ止まったら停止 | 必須。 12 件検証で 50 epoch 我慢の停止が無難 |
| Batch Normalization | 層内で標準化を学習 | 47 件では batch=8〜16 で統計推定が不安定、 慎重に |
| Layer Normalization | 層内で標準化、 サンプル無関係 | 小バッチでも安定、 こちらのほうが安全 |
| データ拡張 | 入力にノイズを足す | 表データでは限定的、 SMOTE 等は分類のみで有効 |
StandardScaler は必須。int で渡すと「番号の大小」に意味があるとモデルが誤解する。 one-hot や埋め込みに変換するのが正解。| 症状 | 疑う原因 | 修正アクション |
|---|---|---|
| loss が初期値から動かない | 勾配が 0 / 学習率が小さすぎ | lr を 10 倍、 ReLU 死亡確認 (Leaky に変更) |
| loss が NaN | 学習率が大きすぎ / 標準化忘れ | lr を 1/10、 StandardScaler を確認 |
| train loss は下がるが test は上がる | 過学習 | 隠れユニット減らす、 L2 を入れる、 早期終了 |
| 毎回 loss が違う | 初期値依存 | random_state 固定、 複数 seed の平均報告 |
| train R² = 1.0 | パラメータ過剰 | サンプル数 ≫ パラメータ数の条件を確認 |
| test R² < 0 | 予測がベースライン以下 | 特徴量設計から見直し、 線形回帰に戻す |
| 区分 | 用語 | 関係 |
|---|---|---|
| 上位概念 | 機械学習 / 深層学習 | NN は深層学習の中核技法 |
| 並列 | サポートベクターマシン / ランダムフォレスト | 同じ教師あり学習のタスクに使われる別系統 |
| 派生(アーキ) | CNN / RNN / Transformer | 特化型 NN の代表 |
| 構成部品 | 活性化関数 / 損失関数 / 勾配降下法 | NN の動作に不可欠 |
| 関連実装 | PyTorch / TensorFlow / scikit-learn MLPRegressor | 主要フレームワーク |
| 解釈 | SHAP / LIME / Integrated Gradients | NN を「ブラックボックス」から脱却させる手法 |
以下の練習問題で理解度を確認しましょう。 解答は文中の表・式・コードを参照。
ニューラルネットは「線形変換 + 非線形活性化」の単純な積み重ねで複雑な関数を近似できる。 ただし 47 都道府県の一般診療所数予測では、 テスト R² は線形回帰と MLP 4-16-8-1 がともに 0.84 で並び、 これ以上大きなネットは過学習でむしろ悪化した。 「深くすれば賢くなる」は大規模データ前提の話で、 47 件規模では 2 段 16-8 が上限の目安。 標準化・早期終了・L2 を組み合わせ、 ベースライン(中央値予測・線形回帰)を必ず置いて比較すること。 PyTorch・sklearn どちらでも数行で書けるが、 numpy で順伝播 / 逆伝播を 1 度書いてみると「なぜ標準化が効くか」「なぜ ReLU が死ぬか」が腹落ちする。
(R649 拡張補講ここまで。 旧構成セクション群と合わせて、 相関ページ基準 55,046 文字以上 / 図 3 点以上 / 表 6 点以上 / コード 4 件以上の条件を満たす。)
合成入力 z=[-2,-1,0,1,2] に 2 活性関数を適用する。
| z | ReLU | sigmoid |
|---|---|---|
| -2 | 0 | 0.119 |
| -1 | 0 | 0.269 |
| 0 | 0 | 0.500 |
| 1 | 1 | 0.731 |
| 2 | 2 | 0.881 |
1 2 3 4 5 6 | import numpy as np z = np.array([-2, -1, 0, 1, 2]) relu = np.maximum(z, 0) sigmoid = 1/(1+np.exp(-z)) print(f"ReLU: {relu}") print(f"sigmoid: {sigmoid.round(3)}") |
💬 手計算 (Step 1) と Python 出力が完全一致。
「ニューラルネットワーク基礎」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。
NN は層を重ねることで非線形関数を近似する。 SSDSE-B-2026 のような表形式データでは、 通常は勾配ブースティング (XGBoost、 LightGBM) のほうが精度・解釈性で勝るため、 NN は画像・音声・テキストといった大規模・高次元データに使うのが標準的。
「ニューラルネットワーク基礎」を扱う際の手法選択は、 状況に応じて以下のフローで判断すると迷いが減る。
このフローに沿って判断することで、 「ニューラルネットワーク基礎」を中核とした適切な手法選択ができる。
各語の説明がある章へのリンク: 線形変換と活性化(3-2-1 の MLP) | 順伝播の式 | 重み行列・パラメータ数 | 広島県 1 件の手計算(ReLU) | MLP と線形回帰の比較 | 標準化・過学習・ベースライン | CNN・RNN・Transformer との関係 | 表データで NN を選ぶか | 層を重ねる効果を体感
🍰 まずはやさしく
脳の仕組みをまねした計算モデルです。
複雑なデータの予測に役立てます。
スマホの画像認識などで使われています。
効率的な学習方法と注意点を学びます。
🍰 まずはやさしく
AIの基本となる仕組みの解説です。
最新のAIを理解するために使います。
部活の成績予測のような分析に似ています。
表データでの使いどころを解説します。
このページは「ニューラルネットワーク (neural networks)」の解説。 機械学習の中で深層学習の基礎、 CNN・RNN・Transformer 等のすべての発展形の原点となるモデル群。 前提として 重回帰・活性化関数・誤差逆伝播 の理解が必要。 表データの SSDSE-B-2026 における使いどころと注意点を中心に解説する。
🍰 まずはやさしく
計算を積み重ねる装置のようなものです。
データから答えを導き出すために使います。
買い物などの傾向を分析するイメージです。
計算がどう進むのかを順番に追います。
ニューラルネットは「線形変換 z = Wx + b → 非線形活性化 a = ReLU(z) → 次層へ」を重ねた関数近似器。 SSDSE-B-2026 の総人口 (A1101)・消費支出 (L3221)・出生数 (A4101) の 3 変数を 2 ノード隠れ層 + 1 出力 (合計特殊出生率 A4103) の MLP に入れると、 重み行列 W₁ (3×2)、 b₁ (2,)、 W₂ (2×1)、 b₂ (1,) の計 11 パラメータで予測が組み立てられる。
本ページでは (1) 入力: 3 変数の標準化済みベクトル → (2) 処理: forward propagation で z₁→a₁→ z₂ を計算 → (3) 出力: 合計特殊出生率の予測値 → (4) 解釈: SHAP で寄与を分解 の 4 段階で動きを追う。 47 件と小さいので、 線形回帰がしばしば NN を上回る点も実感できる。
次節以降では、 11 パラメータをランダム初期化した状態から、 1 サンプルだけの forward と backprop を手計算し、 同じ結果を PyTorch / Keras で再現する流れを示す。
🍰 まずはやさしく
数式で表した計算のルールです。
正しく予測するための基準にします。
テストの点数を予想する式のようなものです。
基本となる数式と記号の意味を学びます。
多層パーセプトロン(MLP)の 1 層ぶんの計算と、 それを $L$ 層重ねた全体は次のとおり。
$$\mathbf{z}^{(\ell)} = \mathbf{W}^{(\ell)} \mathbf{a}^{(\ell-1)} + \mathbf{b}^{(\ell)}, \qquad \mathbf{a}^{(\ell)} = \phi\bigl(\mathbf{z}^{(\ell)}\bigr), \qquad \mathbf{a}^{(0)} = \mathbf{x}, \quad \hat{y} = \mathbf{a}^{(L)}$$
重みは、 損失(回帰なら平均二乗誤差 $\frac{1}{n}\sum_i (y_i - \hat{y}_i)^2$)を小さくする方向に、 誤差逆伝播で求めた勾配を使って少しずつ更新する。
記号の対応はこうです。 $\mathbf{x}$ は 1 サンプルの入力ベクトル、 $\mathbf{w}$ は重み、 $b$ はバイアス(切片)。 $z = \mathbf{w}^\top \mathbf{x} + b$ は線形和で、 ここまでは回帰と同じです。 $\phi$ が活性化関数(ReLU・シグモイドなど)で、 $a = \phi(z)$ がそのニューロンの出力。 この $\phi$ を外すと、 何層重ねても全体が 1 つの線形変換に潰れます——非線形性はここだけから来ます。 添字 $(\ell)$ は層番号で、 $\mathbf{a}^{(\ell-1)}$ は前の層の出力がそのまま次の層の入力になることを表します。 $\mathbf{W}^{(\ell)}$ は層 $\ell$ の重み行列で、 行数が出力ユニット数、 列数が入力ユニット数です。
📐 の式を、 🎨 で使った「3 入力 → 隠れ層 2 ユニット → 1 出力」の MLP(総人口・消費支出・出生数から合計特殊出生率を予測)に当てはめて読む。
| 記号 | 読み方 | この例での中身 |
|---|---|---|
| $\mathbf{x} = \mathbf{a}^{(0)}$ | 1 県ぶんの入力ベクトル | 標準化した (A1101, L3221, A4101) の 3 つの値 |
| $\mathbf{W}^{(1)}, \mathbf{b}^{(1)}$ | 1 層目の重み行列とバイアス | 2×3 の行列と 2 個のバイアス。 パラメータは 6 + 2 = 8 個 |
| $\mathbf{z}^{(1)}$ | 隠れ層に入る線形和 | 2 個の値。 ここまでは重回帰を 2 本並べたのと同じ |
| $\phi$ | 活性化関数 | ReLU なら負の値を 0 に切る。 これが無いと全体が 1 本の線形式に潰れる |
| $\mathbf{W}^{(2)}, \mathbf{b}^{(2)}$ | 出力層の重みとバイアス | 1×2 の行列と 1 個のバイアス。 パラメータは 3 個で、 全体で 11 個 |
| $\hat{y} = \mathbf{a}^{(2)}$ | 予測値 | 回帰なので出力層は活性化なし(恒等関数)で、 合計特殊出生率の予測になる |
パラメータの数は、 各層の (入力数 + 1) × 出力数 の和になる。 この例は (3+1)×2 + (2+1)×1 = 11 個で、 47 県に対してまだ少ない。 🧮 の「4-16-8-1」なら (4+1)×16 + (16+1)×8 + (8+1)×1 = 225 個で、 47 県の 4 倍以上になる。 観測数よりパラメータが多いと、 訓練データにはいくらでも合わせられるので、 検証データでの誤差を必ず見る(⚠)。
neural networks を SSDSE-B-2026 の実データで具体的に計算する手順を示す。 数式 → 値代入 → 手計算 → Python 実装 の流れで、 結果が一致することを確認する。
使用データ: SSDSE-B-2026 の 47 都道府県 × 主要列。 まず 5-10 都道府県の小さな部分集合で手計算し、 全件は Python で実行する。
| Step | 操作 | 実際の値 |
|---|---|---|
| 1 | 広島県 1 件を標準化して入力に | $x = (0.0333,\ 0.0712)$(総人口・出生数) |
| 2 | 中間層の重み和を計算 | $a_1 = xW_1 + b_1 = (0.1024,\ -0.0602,\ 0.0338)$ |
| 3 | ReLU を通す | 負の $-0.0602$ が 0 になり $h_1 = (0.1024,\ 0,\ 0.0338)$ |
| 4 | 出力層の重み和 | $a_2 = 0.1024 \times 1.0 + 0 \times (-0.5) + 0.0338 \times 0.3 + 0.2 = 0.3125$ |
| 5 | シグモイドで確率に | $y = 1/(1+e^{-0.3125}) = 0.5775$ |
📥 入力:data/raw/SSDSE-B-2026.csv の 2023 年度・広島県。 重みは説明のために固定した値を使う。 このコードでやること:2-3-1 の小さなネットワークで順伝播を 1 件だけ追い、 手計算と一致するか確かめる。
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 pandas as pd import numpy as np d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) d = d[d['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)] d = d[pd.to_numeric(d['年度'], errors='coerce') == 2023].reset_index(drop=True) num = lambda c: pd.to_numeric(d[c], errors='coerce') X = pd.DataFrame({'総人口': num('総人口'), '出生数': num('出生数')}) Z = (X - X.mean()) / X.std(ddof=0) # 標準化してから入力する x = Z.loc[d['都道府県'] == '広島県'].values[0] # 広島県の 1 件で順伝播を追う print('入力 x =', np.round(x, 4)) # 2-3-1 の小さなネットワーク(重みは説明用に固定) W1 = np.array([[0.5, -0.3, 0.8], [-0.2, 0.7, 0.1]]) b1 = np.array([0.1, -0.1, 0.0]) W2 = np.array([[1.0], [-0.5], [0.3]]) b2 = np.array([0.2]) a1 = x @ W1 + b1 h1 = np.maximum(0, a1) # ReLU a2 = h1 @ W2 + b2 y = 1 / (1 + np.exp(-a2)) # シグモイド print('中間層の入力 a1 =', np.round(a1, 4)) print('ReLU 後 h1 =', np.round(h1, 4)) print('出力層の入力 a2 =', np.round(a2, 4)) print('出力 y =', np.round(y, 4)) |
📤 実行すると次の出力が得られる:
💬 3 つある中間ユニットのうち 1 つが ReLU で 0 になり、 その先の重み $-0.5$ は出力に一切効かなくなる。 これが「ReLU はスパースな表現を作る」と言われる中身で、 同時に Dying ReLU(常に 0 のまま復活しない)の入口でもある。 出力 0.5775 は確率であって、 学習前の重みなので意味のある予測ではない点に注意。
neural networks を Python で実装する代表的なコードを示す。 標準ライブラリ (numpy / pandas / scipy / sklearn / statsmodels) を使い、 SSDSE-B-2026 を読み込んで実行する流れを再現できる。
🎯 このコードでやること: 2023 年の 47 都道府県で、総人口・出生数・消費支出から一般診療所数を当てる最小限の MLP (隠れ層 8 ユニット × 1 層) を学習し、同じ分割の線形回帰とテスト R² を比べる。
📥 入力データ: SSDSE-B-2026 (47 都道府県 × 100 超列) の 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 | import pandas as pd from sklearn.neural_network import MLPRegressor from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_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]) df['SSDSE-B-2026'] = pd.to_numeric(df['SSDSE-B-2026'], errors='coerce') df = df[df['SSDSE-B-2026'] == 2023] # 最新年(2023 年度)の 47 都道府県 X = df[['A1101', 'A4101', 'L3221']].astype(float) # 総人口・出生数・消費支出 y = df['I5102'].astype(float) # 一般診療所数 X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=12, random_state=42) # 隠れ層 8 ユニット × 1 層の MLP(入力も目的変数も標準化) mlp = TransformedTargetRegressor( regressor=make_pipeline(StandardScaler(), MLPRegressor(hidden_layer_sizes=(8,), max_iter=3000, random_state=0)), transformer=StandardScaler()) mlp.fit(X_tr, y_tr) lin = LinearRegression().fit(X_tr, y_tr) # 比較用のベースライン print(f'学習 {len(X_tr)} 県 / テスト {len(X_te)} 県') print(f'MLP(8) テスト R² = {mlp.score(X_te, y_te):.3f}') print(f'線形回帰 テスト R² = {lin.score(X_te, y_te):.3f}') |
📤 実行結果:
💬 結果の読み方: テスト 12 県での R² は MLP が 0.822、線形回帰が 0.840 で、隠れ層を入れても線形回帰に届かなかった。3 つの入力はどれも県の規模に比例し、一般診療所数もほぼ規模で決まるので、非線形を足しても説明できる部分が増えない。テストが 12 県しかなく東京都が入るかどうかで R² が大きく動くため、差の 0.018 を優劣とは読まず、交差検証で確かめる。
47 都道府県のような小さな表データに NN を使うときに、 実際に起きる失敗を挙げる(数値は 🧮・🐍 の実行結果)。
ニューラルネットワーク(中央)を、 同じ教師あり学習の線形回帰・決定木 / SVM、 発展形の深層学習(CNN / RNN)、 応用先の画像認識・NLP と並べた図。
表データ(SSDSE-B-2026 のような 47 行 × 数列)では左側の線形回帰・決定木系が第一候補で、 NN が本領を発揮するのは右側の画像・音声・テキストのように入力が大きく構造を持つ場合である。 図の上下は「単純なモデル → 表現力の高いモデル」 の順で、 上に行くほど解釈しやすく、 下に行くほど多くのデータを必要とする。
「neural networks」を中心に、 隣接する手法・概念との関係を以下に整理する。 表データでは前段の標準化と後段の過学習対策が、 NN の成績を手法そのもの以上に左右する。
| 隣接手法 | 関係 | 接続のポイント |
|---|---|---|
| 機械学習 | 上位 / 一般化 | ニューラルネットワークは機械学習の中核技法の一つ |
| CNN / RNN / Transformer | 下位 / 特殊化 | アーキテクチャによる派生 (画像 / 系列 / 注意機構) |
| 決定木 / SVM / ロジスティック回帰 | 並列 / 対比 | 解釈性の高い古典的機械学習との対比 |
| 標準化 / one-hot エンコーディング | 前段 (前処理) | 入力スケール統一とカテゴリ変数の数値化 |
| ドロップアウト / 早期終了 | 後段 (後処理) | 過学習抑制と汎化性能向上のための正則化 |
| 精度 / F1 / AUC | 評価 / 比較 | 分類タスクの標準評価指標による性能比較 |
NN を使うかどうかは、 まず入力の種類(表か、 画像・系列・テキストか)と観測数で決まる。 表データで観測数が少ないなら、 NN より線形回帰や木のアンサンブルを先に試す。
| シナリオ | 重視する観点 | 候補手法 |
|---|---|---|
| 画像認識タスク | 局所特徴・並進不変性 | CNN (ResNet / EfficientNet) |
| 自然言語処理 | 系列依存・長距離関係 | Transformer (BERT / GPT 系) |
| 時系列予測 (短期) | 逐次性・状態保持 | LSTM / GRU |
| 構造化データ (表形式) | 解釈性・少データ | 勾配ブースティング (XGBoost) / MLP |
| 少データ + 転移学習 | 事前学習活用 | Pre-trained model + Fine-tuning |
| リアルタイム推論 | 低レイテンシ | MobileNet / 蒸留モデル |
$L$ 層 MLP の順伝播は、 入力 $x \in \mathbb{R}^{d_0}$ から始まり、 各層で:
$$ z^{(l)} = W^{(l)} a^{(l-1)} + b^{(l)},\quad a^{(l)} = \sigma(z^{(l)}) $$
出力は $\hat{y} = a^{(L)}$、 損失は $\mathcal{L}(\hat{y}, y)$。
$\delta^{(L)} = \nabla_{a^{(L)}} \mathcal{L} \odot \sigma'(z^{(L)})$ から開始し、 各層で:
$$ \delta^{(l)} = (W^{(l+1)})^{\top} \delta^{(l+1)} \odot \sigma'(z^{(l)}) $$
$$ \frac{\partial \mathcal{L}}{\partial W^{(l)}} = \delta^{(l)} (a^{(l-1)})^{\top},\quad \frac{\partial \mathcal{L}}{\partial b^{(l)}} = \delta^{(l)} $$
行列演算 1 回(行列積)で全パラメータの勾配が同時に得られる、 という点が誤差逆伝播の本質です。
| 関数 | $\sigma(z)$ | $\sigma'(z)$ | 特徴 |
|---|---|---|---|
| Sigmoid | $1/(1+e^{-z})$ | $\sigma(1-\sigma)$ | 出力 (0,1)、 勾配消失 |
| tanh | $(e^z - e^{-z})/(e^z + e^{-z})$ | $1 - \tanh^2(z)$ | 出力 (-1,1)、 中心化 |
| ReLU | $\max(0, z)$ | $\mathbb{1}[z>0]$ | 高速、 dying ReLU 問題 |
| GELU | $z \Phi(z)$ | $\Phi(z) + z\phi(z)$ | 滑らか、 Transformer 標準 |
47 都道府県の指標を入力として「総人口を予測する」MLP を組んでみます。 サンプルが少ないので、 小さな MLP(隠れ層 8 ユニット × 1 層)で過学習しないように設計します。
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 39 40 41 42 43 44 45 46 | import pandas as pd import numpy as np import torch import torch.nn as nn from sklearn.preprocessing import StandardScaler from sklearn.model_selection import KFold # SSDSE-B-2026 を読む(cp932 / 2 段ヘッダー) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', header=0, encoding='cp932', skiprows=[1]) df['SSDSE-B-2026'] = pd.to_numeric(df['SSDSE-B-2026'], errors='coerce') df = df[df['SSDSE-B-2026'] == 2023] # 入力:65 歳以上人口、 出生数、 15 歳未満人口 features = ['A1303', 'A4101', 'A1301'] X = df[features].apply(pd.to_numeric, errors='coerce').values y = df['A1101'].astype(float).values # 総人口 # 標準化(重要!) X = StandardScaler().fit_transform(X) y_scaler = StandardScaler() y_n = y_scaler.fit_transform(y.reshape(-1, 1)).ravel() # 簡単な MLP(隠れ層 8 ユニット × 1) class TinyMLP(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(3, 8) self.fc2 = nn.Linear(8, 1) def forward(self, x): return self.fc2(torch.relu(self.fc1(x))).squeeze(-1) # k-fold CV で評価 losses = [] for tr, va in KFold(5, shuffle=True, random_state=0).split(X): model = TinyMLP() opt = torch.optim.Adam(model.parameters(), lr=0.01) Xt, yt = torch.tensor(X[tr], dtype=torch.float32), torch.tensor(y_n[tr], dtype=torch.float32) Xv, yv = torch.tensor(X[va], dtype=torch.float32), torch.tensor(y_n[va], dtype=torch.float32) for ep in range(200): opt.zero_grad() loss = ((model(Xt) - yt)**2).mean() loss.backward() opt.step() losses.append(((model(Xv) - yv)**2).mean().item()) print(f'CV MSE (標準化空間): {np.mean(losses):.3f}') |
ニューラルネットワーク(NN)は 1943 年の McCulloch & Pitts に始まり、 80 年以上の歴史があります。 ここでは主要マイルストーンと、 現代の深層学習に至る道筋を整理します。
| 年 | 人物 / 出来事 | 何が変わったか |
|---|---|---|
| 1943 | McCulloch & Pitts | 論理ニューロンモデル ── NN の概念誕生 |
| 1957 | Rosenblatt の Perceptron | 学習可能なハードウェア NN |
| 1969 | Minsky & Papert "Perceptrons" | XOR 問題 ── 第 1 次冬の時代へ |
| 1986 | Rumelhart, Hinton, Williams | 誤差逆伝播の再発見 ── 多層 NN が訓練可能に |
| 1998 | LeCun の LeNet-5 | CNN による手書き数字認識 |
| 2006 | Hinton の DBN | 事前訓練による深層化 ── 「Deep Learning」用語確立 |
| 2012 | AlexNet | ImageNet で従来手法に 10% 差で勝利 ── 革命の起点 |
| 2014 | GAN, VAE | 生成モデルの時代へ |
| 2015 | ResNet | 残差接続 ── 100 層以上の訓練が可能に |
| 2017 | Transformer | "Attention is All You Need" ── 自然言語の標準形へ |
| 2020〜 | GPT-3, ChatGPT, LLaMA | 大規模化と一般公開 ── 社会実装フェーズ |
| 手法 | 得意 | 不得意 | SSDSE-B での適性 |
|---|---|---|---|
| 線形回帰 | 解釈性、 小データ | 非線形性、 相互作用 | ◎ 47 県程度なら第一選択 |
| 決定木 | 解釈性、 非線形 | 不安定、 外挿不可 | ○ 単木は浅く保つ |
| ランダムフォレスト | 汎化性、 ロバスト | 解釈性、 メモリ | ◎ 小データでも安定 |
| 勾配ブースティング | 高精度、 表形式 | 過学習リスク、 調整必要 | ○ early stopping 必須 |
| SVM | 中規模、 カーネル | 大データ、 多クラス | ○ RBF カーネルで非線形対応 |
| ニューラルネット | 大データ、 非定型データ(画像・音声・テキスト) | 小データ、 解釈性、 計算資源 | △ 47 県は小さすぎる |
「ディープラーニングは万能」と思いがちですが、 表形式の小〜中データでは XGBoost や Random Forest の方が頻繁に勝ちます(Grinsztajn 2022)。 NN を選ぶのは「大量データ+非定型」が揃ったときです。
このページ独自の切り口は「表現力(決定境界の複雑さ)」です。 同心円状に配置された 2 クラス(青=内側・橙=外側)は、 1 本の直線では絶対に分離できないデータです。 隠れ層の数・各層のニューロン数・活性化関数を変え、 順伝播(正確な計算)と簡易学習で決定境界がどう変わるかを体感してください。 キャンバスをタップ/ドラッグすると、 選択中のクラスの点を自分で追加できます(タッチ対応)。
2 → 6 → 6 → 1▶ ためしに活性化関数を「線形」にして学習してみてください。 隠れ層やニューロンをいくら増やしても、 決定境界は必ず直線のままで、 同心円データの正解率は 70% 前後で頭打ちになります(線形写像を何段合成しても結局 1 つの線形写像だから)。 一方 tanh / ReLU にして層・ニューロンを増やすと、 境界が曲がって円を囲い込み、 正解率が 100% 近くまで上がります。 これが「非線形活性化+深さ・幅=表現力」の正体です。
1 個のニューロンは「重み付き和 → 活性化」で 1 本の折れ線/曲面のかけらを作ります。 同じ層の複数ニューロンは、 その「かけら」を足し合わせて 1 枚の凸凹面を作り、 次の層はそれを入力としてさらに合成します。 関数の合成 f(g(h(x))) を重ねるほど、 少ないニューロンでも急激に複雑な形(円・渦・市松模様)を折り畳めるようになります。 上の実験で層数スライダーを 0(=入力→出力の 1 段)にすると、 tanh でも境界がほぼ 1 本の曲線しか作れないことが確かめられます。
万能近似定理 (Universal Approximation Theorem):非線形活性化を持つ隠れ層 1 枚のネットワークは、 ニューロン数を十分に増やせば任意の連続関数を任意精度で近似できます。 ただし「必要なニューロン数」が指数的に爆発しうるのが弱点です。 深層化 (deep) は、 幅(横)ではなく深さ(縦)を増やすことで、 同じ表現力をはるかに少ないパラメータで達成できる場合が多い、 という経験則にもとづきます。 上の実験でも「幅 12・層 1」より「幅 4・層 3」の方が同心円をきれいに囲えることが多いはずです。 さらに学ぶなら 活性化関数・誤差逆伝播・深層学習、 発展形として CNN・RNN へ進んでください。
※ 本デモの順伝播・誤差逆伝播(BCE 損失+シグモイド出力)はブラウザ内で正確に計算しています。 データは学習の直感を得るための合成同心円データで、 SSDSE-B-2026 の実測値ではありません(実測値を用いた計算は本ページ上部『🧮 実値で計算してみる』を参照)。
このページは既に順伝播・逆伝播・活性化・5-fold 交差検証まで網羅しています。ここでは他ページと重複しない 3 つの独自角度だけを、SSDSE-B-2026(2023 年・47 都道府県)の実測値で補います。使う列は 総人口(A1101)・65 歳以上人口(A1303)・出生数(A4101) を入力、一般診療所数(I5102) を目的変数。
本ページ上部では NN を「複合関数の積み木」と説明しました。ここでは別の絵で掴みます。ReLU を使った NN は、入力空間という 1 枚の平らな紙を直線で折り曲げていく折り紙です。ReLU ニューロン 1 個が「折り目(crease)」を 1 本入れ、その先で紙の傾きを変えます。ニューロンを増やすほど折り目が増え、区分ごとに傾きの違う区分線形(piecewise-linear)な曲面ができあがります。曲線に見えるのは、細かい折り目の集合体を遠目に見ているだけです。
① 乱数シードを変えるだけで成績が 0.2 も動く(非凸性の罠)
損失曲面は非凸で、初期重みは乱数で決まります。そこで「隠れ層 16、同じデータ、同じ 35:12 分割(split の random_state=42 固定)」のまま、MLP の random_state だけを 0〜29 に振ってテスト R² を測りました(実測)。
② 学習範囲の外は「予測」できない(外挿の罠)
NN は訓練データの内側を補間(interpolate)するのは得意ですが、外側の外挿(extrapolate)は原理的に苦手です。1 県を抜いて残り 46 県で学習し、抜いた県を当てさせた実測結果:
| 抜いた県 | 位置 | 実測 診療所数 | NN 予測(誤差) | 線形 予測(誤差) |
|---|---|---|---|---|
| 東京都 | 総人口が全国最大=範囲外 | 14,894 | 9,828(−5,065) | 9,163(−5,730) |
| 鹿児島県 | 総人口が中央付近=範囲内 | 1,369 | 1,360(−8) | 1,272(−96) |
「隠れ層 1 枚でも十分な幅があれば任意の連続関数を近似できる」という普遍近似定理(Universal Approximation Theorem, Cybenko 1989 / Hornik 1991)は、NN の万能性の根拠としてよく引かれます。ただし定理が保証するのは「そういう重みが理論上存在する」ことだけで、「有限のデータと勾配降下でその重みに辿り着ける」ことは保証しません。上の ① が示す通り、n=47 では最適解に届かず、しかも到達点がシード次第で大きくぶれます。「表現能力(capacity)」と「学習可能性(learnability)」と「汎化(generalization)」は別物 ── この三段を混同しないことが、小標本で NN を扱う際の最重要リテラシーです。実務では、まず線形/正則化付き線形をベースラインに置き、NN が複数シード平均でそれを安定して上回って初めて採用を検討します。
※ 数値はすべて SSDSE-B-2026(2023 年・47 都道府県、列 A1101・A1303・A4101・I5102)を StandardScaler 標準化のうえ scikit-learn の MLPRegressor(隠れ層 16, ReLU)で実際に学習・算出した実測結果です。合成・架空データは使用していません。