ニューラルネットワーク入門
はじめに
本記事は「Pythonで学ぶデータ分析実践」シリーズの補足記事として、ニューラルネットワークの基礎を解説します。
第10回のクラス分類で学んだロジスティック回帰やSVMは強力な手法ですが、複雑な非線形パターンを持つデータには対応が困難な場合があります。ニューラルネットワークは人間の脳の構造を模倣したモデルで、こうした複雑なパターンを学習する能力を持っています。
本記事では、パーセプトロンから多層パーセプトロン、活性化関数、損失関数、最適化手法まで、ニューラルネットワークの基本概念をscikit-learnによる実装とともに解説します。
1. ニューラルネットワークとは
ニューラルネットワークは、人間の脳における神経細胞(ニューロン)のネットワーク構造を模倣した機械学習モデルです。無数のニューロンが繋がり情報伝達を行う仕組みをコンピュータ上で再現し、データからパターンを学習します。
ニューラルネットワークの位置づけ
| 手法 | 特徴 | 適用場面 |
|---|---|---|
| 線形回帰 | 線形関係のみ | 単純な予測 |
| SVM | カーネルで非線形対応 | 中規模データの分類 |
| ニューラルネットワーク | 複雑な非線形パターンを学習 | 画像認識、音声認識、自然言語処理 |
2. パーセプトロン
パーセプトロンはニューラルネットワークの基礎となるアルゴリズムで、複数の入力から1つの出力を返します。出力は「1」(肯定)または「0」(否定)の2値です。
パーセプトロンの仕組み
入力 x₁ ──(重み w₁)──┐
入力 x₂ ──(重み w₂)──┼──→ Σ(重み付き和) ──→ 活性化関数 ──→ 出力(0 or 1)
入力 x₃ ──(重み w₃)──┘ + バイアス b
数式:
$$y = f\left(\sum_{i=1}^{n} w_i x_i + b\right)$$
- $x_i$: 入力値
- $w_i$: 重み(各入力の重要度)
- $b$: バイアス(閾値)
- $f$: 活性化関数
具体例:チョコレートを買うかどうかの意思決定
| 入力 | 条件 | 値 |
|---|---|---|
| $x_1$ | 空腹か | 1(空腹)/ 0(満腹) |
| $x_2$ | 懐に余裕があるか | 1(余裕あり)/ 0(余裕なし) |
| $x_3$ | 甘いもの好きか | 1(好き)/ 0(そうでもない) |
重み付き和が閾値を超えれば「買う(1)」、超えなければ「買わない(0)」と判断します。
import numpy as np
def perceptron(inputs, weights, bias):
"""単純パーセプトロン"""
weighted_sum = np.dot(inputs, weights) + bias
return 1 if weighted_sum > 0 else 0
# チョコレートを買うかの判断
# 甘いもの好きな人の重み設定
weights = np.array([0.3, 0.2, 0.8]) # 空腹, 懐事情, 甘いもの好き
bias = -0.5
# ケース1: 空腹で余裕あり、甘いもの好き
inputs1 = np.array([1, 1, 1])
print(f"ケース1(空腹,余裕あり,甘党): {perceptron(inputs1, weights, bias)} → 買う")
# ケース2: 満腹で余裕なし、甘いもの好き
inputs2 = np.array([0, 0, 1])
print(f"ケース2(満腹,余裕なし,甘党): {perceptron(inputs2, weights, bias)} → {'買う' if perceptron(inputs2, weights, bias) else '買わない'}")
# ケース3: 空腹だが余裕なし、甘いもの好きでもない
inputs3 = np.array([1, 0, 0])
print(f"ケース3(空腹,余裕なし,普通): {perceptron(inputs3, weights, bias)} → {'買う' if perceptron(inputs3, weights, bias) else '買わない'}")
パーセプトロンの限界:XOR問題
単一のパーセプトロンは線形分離可能な問題のみ解けます。XOR(排他的論理和)のような非線形な問題は解けません。
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# AND(線形分離可能)
x_and = np.array([[0,0],[0,1],[1,0],[1,1]])
y_and = np.array([0, 0, 0, 1])
axes[0].scatter(x_and[y_and==0,0], x_and[y_and==0,1], s=100, marker='o', label='0')
axes[0].scatter(x_and[y_and==1,0], x_and[y_and==1,1], s=100, marker='x', label='1')
axes[0].plot([0.5, 0.5, 1.5], [1.5, 0.5, 0.5], 'r--', linewidth=2)
axes[0].set_title("AND(線形分離可能)")
axes[0].legend()
axes[0].set_xlim(-0.5, 1.5)
axes[0].set_ylim(-0.5, 1.5)
# XOR(線形分離不可能)
x_xor = np.array([[0,0],[0,1],[1,0],[1,1]])
y_xor = np.array([0, 1, 1, 0])
axes[1].scatter(x_xor[y_xor==0,0], x_xor[y_xor==0,1], s=100, marker='o', label='0')
axes[1].scatter(x_xor[y_xor==1,0], x_xor[y_xor==1,1], s=100, marker='x', label='1')
axes[1].set_title("XOR(線形分離不可能 → 多層パーセプトロンが必要)")
axes[1].legend()
axes[1].set_xlim(-0.5, 1.5)
axes[1].set_ylim(-0.5, 1.5)
plt.tight_layout()
plt.show()
3. 多層パーセプトロン(MLP)
XOR問題を解決するために、パーセプトロンを階層化するアプローチが提案されました。複数のパーセプトロンを重ね、出力を次の層の入力として連携させる**多層パーセプトロン(MLP: Multi-Layer Perceptron)**が、一般にニューラルネットワークと呼ばれるモデルです。
多層パーセプトロンの構造
入力層 中間層(隠れ層) 出力層
┌─ ニューロン ─┐
入力x₁ ──────┤ ├──────→ 出力y
入力x₂ ──────┤ ニューロン ├
入力x₃ ──────┤ ├
└─ ニューロン ─┘
| 層 | 役割 |
|---|---|
| 入力層 | データを受け取る(特徴量の数 = ニューロン数) |
| 中間層(隠れ層) | データのパターンを学習する |
| 出力層 | 予測結果を出力する |
学習のポイント
ニューラルネットワークが正しく動作するかは重みの値にかかっています。学習とは、予測と正解の誤差が最小になるように重みを調整するプロセスです。
プログラミング時のポイント:
- どのように入力層にデータを渡すか
- どのように中間層を配置するか(層数、ニューロン数)
- どのように出力するか(活性化関数の選択)
- どのように誤差を計算するか(損失関数の選択)
- どのように学習するか(最適化手法の選択)
ディープラーニングとの関係
中間層を複数(深く)積み重ねたニューラルネットワークを**ディープニューラルネットワーク(ディープラーニング)**と呼びます。画像認識、音声認識、自然言語処理などで高い精度を実現しています。
4. 活性化関数
活性化関数は、ニューロンの入力値を変換して出力する関数です。非線形な活性化関数を使うことで、ネットワークが非線形なパターンを学習できるようになります。
主な活性化関数
| 関数 | 出力範囲 | 特徴 | 用途 |
|---|---|---|---|
| Step(ステップ) | 0 or 1 | 閾値を超えたら1、超えなければ0 | 初期のパーセプトロン |
| Sigmoid(シグモイド) | 0〜1 | 滑らかな曲線、確率として解釈可能 | 2値分類の出力層 |
| tanh(ハイパボリックタンジェント) | -1〜1 | 0中心で対称、勾配消失を緩和 | 中間層 |
| ReLU(ランプ関数) | 0〜∞ | 計算が高速、最もよく使われる | 中間層(標準) |
| Softmax(ソフトマックス) | 0〜1(合計1) | 確率分布として出力 | 多クラス分類の出力層 |
活性化関数の可視化
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
x = np.linspace(-5, 5, 200)
fig, axes = plt.subplots(2, 3, figsize=(15, 8))
# Step関数
axes[0,0].plot(x, np.where(x > 0, 1, 0), linewidth=2, color='steelblue')
axes[0,0].set_title("Step(ステップ関数)", fontsize=11)
axes[0,0].set_ylim(-0.1, 1.1)
axes[0,0].grid(alpha=0.3)
axes[0,0].axhline(0, color='gray', linewidth=0.5)
axes[0,0].axvline(0, color='gray', linewidth=0.5)
# Sigmoid関数
sigmoid = 1 / (1 + np.exp(-x))
axes[0,1].plot(x, sigmoid, linewidth=2, color='steelblue')
axes[0,1].set_title("Sigmoid(シグモイド関数)", fontsize=11)
axes[0,1].grid(alpha=0.3)
axes[0,1].axhline(0.5, color='red', linestyle='--', alpha=0.5)
axes[0,1].axvline(0, color='gray', linewidth=0.5)
# tanh関数
axes[0,2].plot(x, np.tanh(x), linewidth=2, color='steelblue')
axes[0,2].set_title("tanh(ハイパボリックタンジェント)", fontsize=11)
axes[0,2].grid(alpha=0.3)
axes[0,2].axhline(0, color='gray', linewidth=0.5)
axes[0,2].axvline(0, color='gray', linewidth=0.5)
# ReLU関数
axes[1,0].plot(x, np.maximum(0, x), linewidth=2, color='steelblue')
axes[1,0].set_title("ReLU(ランプ関数)", fontsize=11)
axes[1,0].grid(alpha=0.3)
axes[1,0].axhline(0, color='gray', linewidth=0.5)
axes[1,0].axvline(0, color='gray', linewidth=0.5)
# Softmax(概念的表示)
softmax_vals = np.exp(x) / np.sum(np.exp(x))
axes[1,1].bar(range(5), [0.05, 0.1, 0.6, 0.15, 0.1], color='steelblue', alpha=0.8)
axes[1,1].set_title("Softmax(出力例: 合計=1)", fontsize=11)
axes[1,1].set_xlabel("クラス")
axes[1,1].set_ylabel("確率")
axes[1,1].grid(axis='y', alpha=0.3)
# 比較
axes[1,2].plot(x, sigmoid, linewidth=2, label='Sigmoid')
axes[1,2].plot(x, np.tanh(x), linewidth=2, label='tanh')
axes[1,2].plot(x, np.maximum(0, x), linewidth=2, label='ReLU')
axes[1,2].set_title("活性化関数の比較", fontsize=11)
axes[1,2].legend()
axes[1,2].grid(alpha=0.3)
axes[1,2].set_ylim(-1.5, 5)
plt.tight_layout()
plt.show()
5. 損失関数
損失関数は、モデルの予測値と正解値の誤差を数値化する関数です。学習の目的は、この損失関数の値を最小化することです。
主な損失関数
| 損失関数 | 用途 | 計算方法 |
|---|---|---|
| 平均二乗誤差(MSE) | 回帰問題 | 予測値と実際の値の差の2乗の平均 |
| 平均二乗平方根誤差(RMSE) | 回帰問題 | MSEの平方根 |
| 二値交差エントロピー | 2値分類 | 予測確率と正解の対数尤度 |
| カテゴリカル交差エントロピー | 多クラス分類 | 各クラスの予測確率と正解の対数尤度 |
損失関数の選択指針
タスクの種類は?
├── 回帰(連続値を予測) → MSE or RMSE
├── 2値分類(0/1) → 二値交差エントロピー
└── 多クラス分類 → カテゴリカル交差エントロピー
6. 最適化手法
損失関数の値を最小化するために重みを更新するプロセスを最適化と呼びます。最適化手法は「どのように重みを更新するか」を決定します。
勾配降下法の基本
重みの更新は以下の式で行われます。
$$w_{new} = w_{old} - \eta \cdot \frac{\partial L}{\partial w}$$
- $\eta$: 学習率(ハイパーパラメータ)— 更新量の大きさを制御
- $\frac{\partial L}{\partial w}$: 損失関数の勾配(微分値)
学習率の影響:
- 大きすぎる → 最小値を飛び越えてしまう(発散)
- 小さすぎる → 学習が遅くなる、局所最小値に陥りやすい
確率的勾配降下法(SGD)
全データではなくランダムに選んだ一部データで勾配を計算し重みを更新する手法です。大規模データでも効率的に学習可能です。
主な最適化手法の比較
| 手法 | 特徴 | 適用場面 |
|---|---|---|
| SGD | シンプル、学習率の設定が重要 | 基本的な学習 |
| Adam | SGD + モーメンタム、最もよく使われる | 大規模データ、汎用的 |
| RMSprop | 勾配の移動平均で学習率を自動調整 | RNN(再帰型NN) |
| L-BFGS | 準ニュートン法ベース | 小規模データ |
| Adagrad | パラメータごとに学習率を自動調整 | スパースデータ |
誤差逆伝搬法(バックプロパゲーション)
誤差逆伝搬法は、出力層で計算された誤差を入力層に向かって逆方向に伝搬し、各層の重みを更新する手法です。
順伝搬: 入力 → 中間層 → 出力(予測値を計算)
↓
誤差計算: 予測値と正解の差(損失)を計算
↓
逆伝搬: 出力層 → 中間層 → 入力層(勾配を計算)
↓
重み更新: 勾配に基づいて全ての重みを更新
↓
繰り返し(損失が十分小さくなるまで)
7. scikit-learnによるニューラルネットワーク
scikit-learnではsklearn.neural_network.MLPClassifierクラスを使って多層パーセプトロンを実装できます。
MLPClassifierの主要パラメータ
| パラメータ | デフォルト値 | 意味 |
|---|---|---|
| hidden_layer_sizes | (100,) | 中間層の構成。例: (100,200) → 2層で各100,200ニューロン |
| activation | 'relu' | 活性化関数 [identity, logistic, tanh, relu] |
| solver | 'adam' | 最適化手法 [sgd, lbfgs, adam] |
| alpha | 0.0001 | L2正則化パラメータ |
| learning_rate_init | 0.001 | 初期学習率(SGD, Adamの場合) |
| max_iter | 200 | 最大エポック数 |
| tol | 1e-4 | 収束判定の閾値 |
| random_state | None | 乱数シード |
| verbose | 0 | 1で詳細ログを表示 |
MLPClassifierの属性・メソッド
| 属性/メソッド | 意味 |
|---|---|
| classes_ | クラスラベル |
| loss_ | 現在の損失値 |
| loss_curve_ | 学習中の損失の推移 |
| fit(X, y) | モデルの学習 |
| predict(X) | 予測の実行 |
| predict_proba(X) | 各クラスの確率を出力 |
| score(X, y) | 正解率を返す |
実装例:Irisデータの分類
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report, accuracy_score
import matplotlib.pyplot as plt
import japanize_matplotlib
# データ準備
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.2, random_state=42, stratify=iris.target
)
# スケーリング(ニューラルネットワークでは必須)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# MLPモデルの構築
mlp = MLPClassifier(
hidden_layer_sizes=(50, 30), # 中間層: 50ニューロン + 30ニューロン
activation='relu', # 活性化関数: ReLU
solver='adam', # 最適化: Adam
alpha=0.001, # 正則化パラメータ
max_iter=1000, # 最大エポック数
tol=0.00001, # 収束判定
random_state=42,
verbose=False
)
# 学習
mlp.fit(X_train_scaled, y_train)
# 予測と評価
y_pred = mlp.predict(X_test_scaled)
print(f"=== ニューラルネットワーク(MLP)による分類結果 ===")
print(f"正解率: {accuracy_score(y_test, y_pred):.4f}")
print(f"\n分類レポート:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))
学習曲線(損失関数の推移)
# 損失関数の推移を可視化
plt.figure(figsize=(10, 5))
plt.plot(mlp.loss_curve_, color='steelblue', linewidth=2)
plt.xlabel("エポック", fontsize=12)
plt.ylabel("損失(Loss)", fontsize=12)
plt.title("学習曲線(損失関数の推移)", fontsize=14)
plt.grid(alpha=0.3)
plt.show()
print(f"最終損失: {mlp.loss_:.6f}")
print(f"学習エポック数: {mlp.n_iter_}")
実装例:手書き数字の分類
from sklearn.datasets import load_digits
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
# 手書き数字データの読み込み
digits = load_digits()
print(f"=== 手書き数字データセット ===")
print(f"データ数: {digits.data.shape[0]}")
print(f"特徴量数: {digits.data.shape[1]}(8×8ピクセル)")
print(f"クラス数: {len(digits.target_names)}(0〜9)")
# データのサンプル表示
fig, axes = plt.subplots(2, 5, figsize=(10, 4))
for i, ax in enumerate(axes.ravel()):
ax.imshow(digits.images[i], cmap='gray')
ax.set_title(f"ラベル: {digits.target[i]}")
ax.axis('off')
plt.suptitle("手書き数字データのサンプル", fontsize=13)
plt.tight_layout()
plt.show()
# データ分割とスケーリング
X_train, X_test, y_train, y_test = train_test_split(
digits.data, digits.target, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# MLPモデルの構築と学習
mlp_digits = MLPClassifier(
hidden_layer_sizes=(128, 64),
activation='relu',
solver='adam',
max_iter=500,
random_state=42
)
mlp_digits.fit(X_train_scaled, y_train)
# 評価
y_pred = mlp_digits.predict(X_test_scaled)
print(f"\n正解率: {accuracy_score(y_test, y_pred):.4f}")
# 混同行列
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=digits.target_names, yticklabels=digits.target_names)
plt.xlabel("予測ラベル")
plt.ylabel("正解ラベル")
plt.title(f"混同行列(正解率: {accuracy_score(y_test, y_pred):.4f})")
plt.tight_layout()
plt.show()
ハイパーパラメータの調整
from sklearn.model_selection import GridSearchCV
# グリッドサーチでパラメータ最適化
param_grid = {
'hidden_layer_sizes': [(50,), (100,), (50, 30), (100, 50)],
'activation': ['relu', 'tanh'],
'alpha': [0.0001, 0.001, 0.01],
}
grid_search = GridSearchCV(
MLPClassifier(max_iter=500, random_state=42),
param_grid,
cv=3,
scoring='accuracy',
n_jobs=-1,
verbose=0
)
grid_search.fit(X_train_scaled, y_train)
print(f"=== グリッドサーチ結果 ===")
print(f"最適パラメータ: {grid_search.best_params_}")
print(f"最高スコア(CV): {grid_search.best_score_:.4f}")
print(f"テストスコア: {grid_search.score(X_test_scaled, y_test):.4f}")
8. 学習の終了条件
ニューラルネットワークの学習は、以下の条件で終了します。
| パラメータ | 意味 |
|---|---|
| max_iter | 最大エポック数を超えた場合に終了 |
| tol | 損失関数の改善量がこの値を下回るエポックが連続した場合に収束と判断 |
# 学習の終了条件の例
mlp = MLPClassifier(
hidden_layer_sizes=(100,),
max_iter=10000, # 最大10000エポック
tol=0.00001, # 損失の改善が0.00001未満が続いたら収束
random_state=42
)
mlp.fit(X_train_scaled, y_train)
print(f"実際のエポック数: {mlp.n_iter_}")
print(f"最終損失: {mlp.loss_:.6f}")
if mlp.n_iter_ < 10000:
print("→ tolの条件で収束して学習終了")
else:
print("→ max_iterに達して学習終了(収束していない可能性あり)")
まとめ
本記事では、ニューラルネットワークの基礎として以下の内容を解説しました。
| 項目 | ポイント |
|---|---|
| パーセプトロン | 入力の重み付き和と閾値で2値分類。線形分離のみ対応 |
| 多層パーセプトロン | 層を重ねて非線形パターンを学習。ディープラーニングの基盤 |
| 活性化関数 | 非線形性を導入。ReLUが中間層の標準、Softmaxが多クラス出力 |
| 損失関数 | 予測と正解の誤差を数値化。回帰→MSE、分類→交差エントロピー |
| 最適化手法 | 重みの更新方法。Adamが最も汎用的 |
| 誤差逆伝搬法 | 誤差を逆方向に伝搬して全層の重みを更新 |
| scikit-learn実装 | MLPClassifierで手軽に実装可能 |
ニューラルネットワーク実装のチェックリスト
- データの前処理(スケーリング)を行ったか
- 適切な中間層の構成(層数、ニューロン数)を設定したか
- 活性化関数を適切に選択したか
- 最適化手法と学習率を設定したか
- 学習曲線で収束を確認したか
- 過学習していないか確認したか(学習/テストの精度差)
- ハイパーパラメータの調整を行ったか