機械学習

目次

概要

データ、損失、汎化、評価をひとつの流れとして理解する

機械学習は、アルゴリズム名を並べる学問ではありません。データをどう表し、どんなずれを減らし、未知データでどこまで通用するかを確かめる一連の設計です。この章では、回帰・分類・木・アンサンブル・教師なし学習までを、実務に近い流れで整理します。

要点

機械学習で重要なのは、モデル選びの前にデータ、損失、分割、リーク、評価、汎化を押さえることです。モデルはその上に載る道具でしかありません。

この章で重視すること

  • データから運用までを1本のループとして捉える
  • 回帰と分類を「予測の型」として整理する
  • モデル名より、どんな失敗を防ぐかを優先して理解する

機械学習とは何か

機械学習は、データから規則を学び、未知データに対して予測や判断を行う方法です。

flowchart LR A["データ"] --> B["前処理"] B --> C["モデル"] C --> D["予測"] D --> E["評価"] E --> F["改善"] F --> B

このループのどこが弱いかを言語化できることが、単にモデル名を知っていることより重要です。

データと特徴量

機械学習では、

  • 1行 = 1サンプル
  • 1列 = 1特徴量
  • 予測したい列 = ラベル

という整理が基本です。

ここでよく起きる論点:

  • 欠損をどう扱うか
  • カテゴリ変数をどう数値化するか
  • スケーリングが必要か
  • 学習時に見えていた情報が本番でも見えるか

特に リーク は重要です。本番で見えない情報が訓練や評価に混ざると、精度が高く見えても実運用では壊れます。

損失と学習

学習では、「どのくらい外れたか」を数で測る必要があります。これが損失です。

  • 回帰: 二乗損失、絶対損失
  • 分類: 交差エントロピー

損失が決まると、どちらへ更新すればよいかを勾配で読めます。ここで大事なのは、

  • 何を減らしたいのか
  • その損失は外れ値に強いか
  • 予測確率の出し方と整合しているか

です。

回帰

回帰は、連続値を予測する問題です。最初の入口は線形回帰です。

  • 売上予測
  • 家賃予測
  • 需要予測

線形回帰から見えるもの:

  • 特徴量の寄与
  • 正則化の意味
  • 過学習の入り口

より実務寄りには、

  • Ridge / Lasso / Elastic Net
  • ロバスト回帰
  • 分位点回帰

の考え方が重要になります。

分類

分類は、クラスを予測する問題です。二値分類なら、まず確率を出してから閾値でクラスに変える、と考えるのが自然です。

flowchart LR A["特徴量x"] --> B["モデル"] B --> C["確率p"] C --> D["閾値tau"] D --> E["クラス0 / 1"]

ロジスティック回帰は、分類の入口としてとても重要です。ここでは

が一緒に学べます。

汎化とモデル選択

訓練データでうまくいったことと、未知データで通用することは別です。これが汎化の問題です。

重要な論点:

  • train / validation / testの役割
  • 交差検証
  • 過学習とアンダーフィット
  • ハイパーパラメータ探索
  • ベースラインを持つこと

機械学習では、モデルの複雑さを上げることより、「評価の作法」を崩さないことの方が重要な場面が多いです。

評価指標

良いモデルかどうかは、目的によって指標が変わります。

不均衡データではAccuracyが役に立たないことがよくあります。何を間違えると痛いのかを先に決めてから指標を見る必要があります。

木とアンサンブル

表データでは、決定木、Random Forest、GBDT系が非常に強いです。

それぞれの見え方:

  • 決定木: 分岐でルールを作る
  • Random Forest: 木をたくさん作って平均する
  • GBDT: 間違いを順番に修正していく

木系モデルは、

  • 前処理依存が比較的少ない
  • 欠損や非線形に強い
  • 表データで強力

という利点があります。

教師なし学習

ラベルがないときは、構造を探す問題になります。

  • PCA: 次元圧縮
  • クラスタリング: 似たもの同士をまとめる
  • 密度推定: 珍しさや異常の見方

教師なし学習では、「正解がないので評価が難しい」という点が、教師ありと大きく違います。

実務でよく起きる失敗

  • リーク
  • train / testの分割ミス
  • 指標の選び方が目的とずれている
  • 本番入力分布の変化を無視する
  • 予測確率をそのまま意思決定に使ってしまう
  • ベースラインより複雑なだけのモデルを選ぶ
よくある誤解

高性能なモデルを選べば問題が解決するわけではありません。実際には、データ設計、分割、指標、運用監視の方が性能差より効くことが多いです。

前処理パイプライン

機械学習では、前処理を学習時と推論時で一致させることが重要です。notebook上で手作業の前処理を行い、本番で別実装にすると、training-serving skewが起きます。

前処理には次が含まれます。

  • 欠損補完
  • カテゴリ変数のencoding
  • 数値スケーリング
  • 外れ値処理
  • 特徴量生成
  • 日付やテキストの変換

scikit-learnのPipelineのように、前処理とモデルを1つの流れとして扱うと、交差検証でも本番化でも漏れが減ります。

クラス不均衡

分類では、陽性が1%しかないような問題がよくあります。この場合、全てを陰性と予測してもaccuracyは99%になりますが、目的には合いません。

不均衡データでは次を検討します。

  • confusion matrixを見る
  • precisionとrecallを分ける
  • PR-AUCを見る
  • class weightを使う
  • thresholdを業務目的で調整する
  • false positiveとfalse negativeのコストを明示する

指標は技術的な数字ではなく、業務上の誤りコストを反映する必要があります。

キャリブレーション

分類モデルが 0.8 と出したとき、本当に約80%の確率で当たるとは限りません。確率出力を意思決定に使うなら、キャリブレーションが重要です。

用途 キャリブレーションの重要度
上位候補を並べるだけ
閾値で自動承認する
リスクや期待値を計算する

確率が過信気味か控えめかを確認し、必要ならcalibration curveやBrier scoreを見ます。

解釈と説明

モデル解釈は「なぜそう予測したか」を理解するための手段です。ただし、説明手法も近似であり、万能ではありません。

  • 線形モデルの係数
  • tree系のfeature importance
  • permutation importance
  • SHAPなどの局所説明
  • partial dependence

説明は、モデル改善、監査、利用者への説明、異常検知に役立ちます。一方で、説明が分かりやすいことと、モデルが正しいことは別です。

統計学習理論の基礎

リスク最小化の視点

「The Elements of Statistical Learning」(ISLR: An Introduction to Statistical Learning, statlearning.com)と、 Stanford CS229(cs229.stanford.edu)の観点から、機械学習をリスク最小化として捉えます。

期待リスクと経験リスク

定式化:

期待リスク R(f) = E[L(Y, f(X))]
経験リスク R_hat(f) = (1/n) Σ L(Y_i, f(X_i))

[!note] 機械学習の目標は、期待リスク R(f) を最小化することですが、 訓練データから知り得るのは 経験リスク R_hat(f) だけです(statlearning.com)。

この「見えないリスク」を推定するために、汎化理論が生まれました。

汎化誤差の上界(Sample Complexity)

Rademacher複雑度(Rademacher Complexity)を使うと、下界が保証できます:

P(R(f)R_hat(f) + O(√(log(1/δ)/2m))) ≥ 1 - δ

ここで m はサンプル数。つまり:

  • サンプルが増えると上界は√mのオーダーで縮小
  • より複雑なモデルクラスだと上界が増加
  • 信頼度を上げると上界が増加

統計学習理論における3つの誤り

(statlearning.comとCS229から)

誤りの種類 原因 対策
Bias モデルが過度に単純 モデル複雑度を上げる
Variance モデルが訓練データに過適合 正則化・データ増加
Irreducible Error データ自体のノイズ 改善不可能

Bias-Variance トレードオフは、汎化誤差の基本的な制約です。

graph LR A["モデル複雑度"] --> B["Bias↓"] A --> C["Variance↑"] D["Bias-Variance Tradeoff"] B --> D C --> D

VC次元(Vapnik-Chervonenkis Dimension)

VC次元は、モデルが「粉砕できる(shatter)」最大サンプル数です。

例: 平面上の直線

- 2点: どの分離も可能 → VC > 2
- 3点: 一般位置(general position)なら可能 → VC > 3  
- 4点: 一般位置ならできない点配置がある → VC = 3

VC次元が高いほど、汎化誤差の上界が悪くなります。

正則化の理論的正当性

最小化: R_hat(f) + λ · Ω(f)

ここで λ はペナルティの強さ、Ω(f) はモデルの複雑度。

  • L1正則化(Lasso): スパースなモデル(係数 0 が多い)
  • L2正則化(Ridge): 係数全体を小さく
  • Elastic Net: 両者の併用

正則化定数 λ を大きくするほど、ビアスが増え、バリアンスが減ります。

交差検証の統計的意味

k-fold交差検証では:

CV = (1/k) Σ L(fold_i)

各foldは独立サンプルとして機能し、 CV は 期待リスク R(f) の不偏推定量に近づきます。

層化k分割(Stratified k-fold)を使うと、クラス不均衡時の推定が安定します。

Stanford CS229 の主要な学習アルゴリズム

Stanford CS229 (cs229.stanford.edu)では、以下のアルゴリズムを深く扱います。

1. 線形回帰と正規方程式

予測: h(x) = θ^T x
目的: J(θ) = (1/2m) Σ(h(x_i) - y_i)^2
解: θ = (X^T X)^{-1} X^T y  (正規方程式)

計算量: O(n^3) だが、 m >> n の場合は勾配降下法を使用。

2. ロジスティック回帰

h(x) = 1 / (1 + exp(-θ^T x))
目的: J(θ) = -(1/m) Σ [y_i log h_θ(x_i) + (1-y_i) log(1-h_θ(x_i))]

勾配:

J(θ) = (1/m) X^T (h(x) - y)

アップデート: θ := θ - α ∇J(θ)

3. 決定木とエントロピー

決定木では、各ノードで**情報利得(Information Gain)**最大の分割を選択:

IG = H(parent) - Σ (|child|/|parent|) H(child)
H(p) = -Σ p_i log_2(p_i)  (エントロピー)

木の深さと葉数はハイパーパラメータ。

4. ナイーブベイズ分類

独立性の仮定:

P(Y | X_1, ..., X_n) ∝ P(Y) Π P(X_i | Y)

最大事後確率(MAP)推定:

Y_pred = argmax_y P(Y=y) Π P(X_i=x_i | Y=y)

5. サポートベクトルマシン(SVM)

マージン最大化:

最小化: (1/2) ||w||^2 + C Σ ξ_i
制約: y_i(w^T φ(x_i) + b) ≥ 1 - ξ_i

カーネルトリック で非線形分離を実現:

K(x_i, x_j) = exp(-γ ||x_i - x_j||^2)  (RBFカーネル)

6. K-means クラスタリング

繰り返し:

E-step: 各x_i を最近心に割り当て
M-step: 心 μ_k を更新 μ_k := (1/n_k) Σ_{x_i in cluster k} x_i

K-means++ で初期化を改善できます。

7. EM アルゴリズム

潜在変数 z がある場合:

E-step: Q(z | x, θ) = P(z | x, θ_old)  を計算
M-step: θ_new = argmax Σ Q(z | x, θ) log P(x, z | θ)

GMM(ガウス混合モデル)はEMの典型例。

実務における機械学習パイプライン

データリークの検出と対策

WARNING

リークは「見えない失敗」です。精度報告は高く見えても、本番で崩壊します。

典型的なリーク:

# ×悪い例: テスト用のスケーラを訓練データ全体で学習
scaler = StandardScaler().fit(X)  # 全データで学習
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

# ○正しい例: 訓練データだけで学習
scaler = StandardScaler().fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

scikit-learnのPipelineはこれを自動化:

pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('model', LogisticRegression())
])
cross_val_score(pipe, X, y, cv=5)  # リークなし

ハイパーパラメータ探索戦略

GridSearchCV か RandomizedSearchCV を用いますが、 層化k分割と組み合わせることが重要です。

from sklearn.model_selection import GridSearchCV, StratifiedKFold
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
gs = GridSearchCV(model, param_grid, cv=cv, scoring='f1')

本番環境への移行

訓練から本番へ、以下の監視が必須:

  • Feature drift: 入力分布が変わった
  • Label drift: 予測対象が変わった
  • Prediction drift: モデル出力が変わった
  • Performance drift: 精度が落ちた

これらは機械学習システムの「データ品質」に関わります。

非統計的評価フレームワーク

混同行列(Confusion Matrix)の読み方

              予測positive   予測negative
実際positive  TP            FN
実際negative  FP            TN
  • Precision = TP / (TP + FP): 「positiveと言った時の正確さ」
  • Recall = TP / (TP + FN): 「本当のpositiveの何%を捕捉したか」
  • F1 = 2·Precision·Recall / (Precision + Recall): バランス指標
  • Specificity = TN / (TN + FP): 「negativeを正しく棄却した率」

ROC曲線とPR曲線

ROC曲線: Xc軸が偽陽性率(FPR), Y軸が真陽性率(TPR)

  • ROC-AUC = 1.0 : 完璧
  • ROC-AUC = 0.5 : ランダム分類と同等
  • ROC-AUC < 0.5 : モデル反転で改善

PR曲線: X軸がRecall, Y軸がPrecision

  • 不均衡データではPR-AUCの方が信頼できる
  • PR-AUCが 1.0 に近くても、閾値を動かすと性能が変わる可能性

高度なトピック: ランダム性の制御と再現性

機械学習コードの再現性はしばしば見落とされるが、本番運用では重要です。scikit-learnではrandom_stateパラメータが乱数生成を制御します。

乱数状態の管理

整数を渡すと毎回同じ結果が得られます:

from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(random_state=42)  # 常に同じ
rf.fit(X, y)

一方、RandomStateインスタンスを渡すと、呼び出すたびに異なる結果が得られます:

import numpy as np
rng = np.random.RandomState(42)
rf = RandomForestClassifier(random_state=rng)
rf.fit(X, y)  # 1回目の結果
rf.fit(X, y)  # 2回目は異なる

交差検証の堅牢性を重視する場合、estimatorにRandomStateインスタンスを渡します。これにより、各foldで異なるランダム初期化が使われ、特定のシードに依存しません。一方、CV splitterには整数を渡すことが推奨されます。

Pipelineによる前処理リークの防止

データリークの最大の原因は前処理の不正な適用です。標準化(StandardScaler)を全データに対して実行した後に訓練・テスト分割すると、テストデータの統計情報が訓練に漏洩します。

誤った例:

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)  # 全データでfit
X_train, X_test = train_test_split(X_scaled)

正しい例:

from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression

pipeline = make_pipeline(StandardScaler(), LogisticRegression())
pipeline.fit(X_train, y_train)  # X_trainのみで学習
y_pred = pipeline.predict(X_test)

Pipelineは特徴選択、次元削減、正規化など全ての前処理に有効です。Pipelineを使うことで、fit_transformtransformの使い分けが自動化されます。

特徴選択とデータリーク

特徴選択もリークの原因になります。10,000個の乱数特徴量と無関係なターゲットで実験すると:

リークあり(精度0.76):

from sklearn.feature_selection import SelectKBest
X_selected = SelectKBest(k=25).fit_transform(X, y)  # 全データで選択
X_train, X_test = train_test_split(X_selected, y)
# accuracy_score: 0.76 (不正に高い)

リークなし(精度0.5):

X_train, X_test = train_test_split(X, y)
selector = SelectKBest(k=25)
X_train_sel = selector.fit_transform(X_train, y_train)
X_test_sel = selector.transform(X_test)
# accuracy_score: 0.5 (期待値)

訓練データに対してのみ特徴選択を行う場合でも、Pipelineで自動化が推奨されます。

データドリフト監視と本番運用

本番環境では、訓練時と異なる分布のデータが到着します。これを検出する仕組みが必要です。

ドリフトの4つのカテゴリ

  1. 特徴ドリフト: 入力分布 P(X) が変化(ユーザー層変化、データソース変更)
  2. ラベルドリフト: 目的変数 P(Y) が変化(季節性、トレンド)
  3. 概念ドリフト: 関係 P(Y|X) が変化(経済サイクル、規制変更)
  4. 予測ドリフト: モデル出力の分布が変化(特徴ドリフトの結果)

ドリフト検出の実装

統計的距離を用いた検出:

from scipy.stats import ks_2samp
import numpy as np

# 訓練期間と直近期間を比較
ks_stat, p_value = ks_2samp(X_train[:, 0], X_recent[:, 0])
if p_value < 0.05:
    print("特徴ドリフトを検出")
    
# より堅牢: Wasserstein距離
from scipy.stats import wasserstein_distance
w_dist = wasserstein_distance(X_train[:, 0], X_recent[:, 0])

カテゴリ変数のドリフト検出には多項分布の逆距離(Hellinger距離)が使われます。

本番環境でのモニタリング戦略

  1. 予測信頼度の低下: 予測確率の高い判定でも実際精度が低下
  2. 評価指標の乖離: 訓練時の指標と本番の指標の乖離
  3. 入力特徴の外れ値化: 異常検知モデルで入力を監視
  4. 定期的な再学習: ドリフト検出時に自動再学習トリガー

コンセプトドリフトが検出された場合、以下の対応が考えられます:

  • 最近のデータで即座に再学習
  • オンライン学習アルゴリズムへの切り替え(SGDClassifier等)
  • モデルアンサンブルで複数時期のモデルを並行運用

実装レベル: scikit-learn での詳細フロー

機械学習の実装では、以下のベストプラクティスが重要です。

完全なクロスバリデーションパイプライン

from sklearn.model_selection import cross_validate, StratifiedKFold
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from sklearn.metrics import (
    accuracy_score, precision_score, recall_score, f1_score,
    roc_auc_score, confusion_matrix
)
import numpy as np

# Pipelineの構築(前処理 + モデル)
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', RandomForestClassifier(
        n_estimators=100,
        max_depth=10,
        random_state=42,
        n_jobs=-1
    ))
])

# Stratified K-Fold: クラス分布を保持
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# 複数メトリクスを同時に計算
scoring = {
    'accuracy': 'accuracy',
    'precision': 'precision_weighted',
    'recall': 'recall_weighted',
    'f1': 'f1_weighted',
    'roc_auc': 'roc_auc_ovr'
}

results = cross_validate(
    pipeline, X, y,
    cv=cv,
    scoring=scoring,
    return_train_score=True,
    n_jobs=-1
)

# 結果の集約
for metric in scoring.keys():
    train_scores = results[f'train_{metric}']
    test_scores = results[f'test_{metric}']
    print(f"{metric}: {test_scores.mean():.3f} +/- {test_scores.std():.3f}")
    print(f"  Overfitting gap: {(train_scores.mean() - test_scores.mean()):.3f}")

Grid Search によるハイパーパラメータチューニング

from sklearn.model_selection import GridSearchCV

param_grid = {
    'model__n_estimators': [50, 100, 200],
    'model__max_depth': [5, 10, 15, None],
    'model__min_samples_split': [2, 5, 10],
    'model__min_samples_leaf': [1, 2, 4]
}

grid_search = GridSearchCV(
    pipeline,
    param_grid,
    cv=5,
    scoring='f1_weighted',
    n_jobs=-1,
    verbose=1
)

grid_search.fit(X_train, y_train)

print(f"Best params: {grid_search.best_params_}")
print(f"Best CV score: {grid_search.best_score_:.3f}")

# テストセットで評価
y_pred = grid_search.predict(X_test)
print(f"Test accuracy: {accuracy_score(y_test, y_pred):.3f}")
print(f"Confusion matrix:\n{confusion_matrix(y_test, y_pred)}")

クラス不均衡への対応

from sklearn.utils.class_weight import compute_class_weight
from sklearn.metrics import classification_report

# クラス重み自動計算
class_weights = compute_class_weight(
    'balanced',
    classes=np.unique(y),
    y=y
)
class_weight_dict = dict(enumerate(class_weights))

model = RandomForestClassifier(
    class_weight=class_weight_dict,
    random_state=42
)

# SMOTE による過剰サンプリング(alternative)
from imblearn.over_sampling import SMOTE

smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

model.fit(X_resampled, y_resampled)

# 詳細な評価レポート
print(classification_report(y_test, y_pred))

正則化とリッジ/ラッソ回帰

過剰適合を抑制するため、損失関数に正則化項を追加:

L2 正則化(リッジ回帰)

最小化: MSE(w) + λ * ||w||²
from sklearn.linear_model import Ridge, RidgeCV

# λ(alpha)の最適値を自動選択
ridge_cv = RidgeCV(alphas=[0.1, 1.0, 10.0, 100.0], cv=5)
ridge_cv.fit(X_train, y_train)
print(f"Best alpha: {ridge_cv.alpha_}")

ridge = Ridge(alpha=ridge_cv.alpha_)
ridge.fit(X_train, y_train)
train_score = ridge.score(X_train, y_train)
test_score = ridge.score(X_test, y_test)
print(f"Train R²: {train_score:.3f}, Test R²: {test_score:.3f}")

L1 正則化(ラッソ回帰)

最小化: MSE(w) + λ * ||w||_1

ラッソは特徴量選択も行う(係数がゼロになる):

from sklearn.linear_model import LassoCV

lasso_cv = LassoCV(cv=5, max_iter=10000)
lasso_cv.fit(X_train, y_train)

# 非ゼロ係数の数
non_zero = np.sum(lasso_cv.coef_ != 0)
print(f"Selected features: {non_zero}/{X.shape[1]}")

Elastic Net(L1 + L2)

最小化: MSE(w) + λ₁ * ||w||_1 + λ₂ * ||w||²

L1 と L2 の利点を組み合わせ:

from sklearn.linear_model import ElasticNetCV

elastic = ElasticNetCV(l1_ratio=[0.1, 0.5, 0.9], cv=5)
elastic.fit(X_train, y_train)
print(f"L1 ratio: {elastic.l1_ratio_}")

特徴量エンジニアリングの実装

データの質 = モデル性能の 80%

ポリノミアル特徴量の生成

from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X)

print(f"Original features: {X.shape[1]}")
print(f"Polynomial features: {X_poly.shape[1]}")
# degree=2 で、元の n 特徴 → n + nC2 = n(n+1)/2 + n 特徴へ

特徴量スケーリングの影響

from sklearn.preprocessing import (
    StandardScaler, MinMaxScaler, RobustScaler, PowerTransformer
)

# StandardScaler: 平均0、分散1
scaler1 = StandardScaler()
X_standard = scaler1.fit_transform(X_train)

# MinMaxScaler: [0, 1] 範囲
scaler2 = MinMaxScaler(feature_range=(0, 1))
X_minmax = scaler2.fit_transform(X_train)

# RobustScaler: 外れ値に強い
scaler3 = RobustScaler()
X_robust = scaler3.fit_transform(X_train)

# PowerTransformer: 歪んだ分布を正規化
power = PowerTransformer()
X_power = power.fit_transform(X_train)

距離ベースのモデル(KNN, SVM)では StandardScaler 推奨。 ツリーベースのモデル(決定木)ではスケーリング不要。

実装チェックリスト

本番デプロイ前の確認事項:

  1. データリーク検査

    • 訓練集合と評価集合が分離されているか?
    • 前処理は訓練集合のみで学習しているか?
  2. クラス不均衡対応

    • クラス分布を確認したか?
    • F1スコア、PrecisionRecall で評価しているか?
  3. 外れ値検査

    • 入力の統計量(min, max, 四分位数)を確認?
    • 極端な値の扱いを決めたか?
  4. 特徴量の多重共線性

    • VIF(分散膨張係数)を計算したか?
    • 高い相関の特徴量を検出したか?
  5. モデル検証

    • Stratified K-Fold で評価したか?
    • 訓練・テスト曲線(Learning Curve)をプロット?
    • 異なるランダムシードで再現性を確認?
  6. 本番環境対応

    • 入力の前処理と同じパイプラインを デプロイ環境に含めたか?
    • ログ・監視体制は整備したか?
    • ドリフト検出の仕組みはあるか?

CS229 で教える主要アルゴリズム(Stanford ML コース体系)

Stanford CS229 では、以下の構造で機械学習を段階的に解説します。これは実務での学び順序とも一致します。

教師ありモデルの段階的学習

1. 線形モデルの基礎(週 1-2)

  • Least Mean Squares (LMS): シンプルな回帰の更新則

    • パラメータ更新: θ := θ + α(y - h(x))x
    • 確率的勾配降下法と一括勾配降下法の違い
  • 重み付き最小二乗 (Weighted Least Squares): 異なるサンプルに異なる重要度を付与

    • コスト関数: Σ w_i * (y_i - w^T x_i)^2
    • 堅牢な回帰を実現

2. ロジスティック回帰と一般化線形モデル(週 3-4)

  • Newton’s Method による効率的な最適化
    • ヘッシアン行列を用いた 2 階微分ベース更新
    • 線形回帰の勾配降下法より高速に収束
# ニュートン法のパラメータ更新
# θ := θ - H^(-1) ∇J(θ)
# H は Hessian 行列(2階微分)
  • 指数族分布と十分統計量の概念
    • ポアソン分布、ガウス分布、ベルヌーイ分布を統一フレームワークで扱う

3. 生成モデル vs 判別モデル(週 5)

  • ガウス判別分析 (GDA): p(x|y) をモデル化
    • クラス 0, 1 それぞれに多変量ガウス分布を仮定
    • 閉形式解で最適パラメータを直接計算
    • 小サンプルでも安定(仮定が強い場合に有利)
GDA での境界: ln(P(y=1|x) / P(y=0|x)) = w^T x + b
  • ナイーブベイズ: 特徴の条件独立を仮定
    • p(y=1) = π
    • p(x_j|y) を特徴ごとに独立に推定
    • テキスト分類、スパムフィルタの定番
    • ラプラススムージング: p(x_j=k|y) = (count + 1) / (total + K)

4. サポートベクターマシン(週 6-7)

  • Kernel trick により非線形分類が可能に

    • 線形分類器の双対形式: min_α Σα_i - 0.5 Σ α_i α_j y_i y_j K(x_i, x_j)
    • K(x, x’) = φ(x)^T φ(x’) を直接計算(φ の明示的構築不要)
  • よく使われるカーネル:

    • 線形: K(x, x’) = x^T x’
    • 多項式: K(x, x’) = (x^T x’ + c)^d
    • RBF (Radial Basis Function): K(x, x’) = exp(-γ ||x - x’||²)
from sklearn.svm import SVC
svm = SVC(kernel='rbf', gamma=0.1, C=1.0)
# γ が大きい = より局所的な判定
# C が小さい = より余裕を持たせる(過学習抑制)

5. 教師なし学習(週 8)

  • K-means: ハード割り当てによるクラスタリング

    • E-step: 各点を最近のセントロイドに割り当て
    • M-step: 各クラスタの平均でセントロイドを更新
    • 初期化に左右されやすい(k-means++ で改善)
  • ガウス混合モデル (GMM): 確率的クラスタリング

    • 期待値最大化アルゴリズム (EM) で学習
    • 各点がどのガウス分布に属する確率も計算可能
    • BIC/AIC で最適なクラスタ数を選択
from sklearn.mixture import GaussianMixture
gmm = GaussianMixture(n_components=3, covariance_type='full')
gmm.fit(X)
print(f"BIC: {gmm.bic(X)}")

実務での落とし穴と対策

scikit-learn の “Common pitfalls and recommended practices” が指摘する 3 つの大問題:

1. 不整合な前処理(Inconsistent Preprocessing)

問題: 訓練データの統計量で正規化した変換器を、テストデータで同じ変換器で処理しないと、本番でずれが生じる。

# 誤った方法
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.fit_transform(X_test)  # ← X_test で再度 fit(NG)

# 正しい方法
scaler = StandardScaler()
X_train_scaled = scaler.fit(X_train).transform(X_train)
X_test_scaled = scaler.transform(X_test)  # fit は訓練のみ

2. データリーク(Data Leakage)

問題: 本番では入手不可の情報が訓練に混在すると、精度が高く見えても実運用で壊れる。

例:

  • クレジットカード詐欺検出: 「取引をキャンセルした」という後発情報が特徴に含まれていないか?
  • 医療診断: 「実際の検査結果の一部」が特徴に混入していないか?

対策:

  • パイプライン (sklearn.pipeline.Pipeline) を使い、前処理と学習を一体化
  • 時系列データは “futures leakage” を防ぐため時間順で分割
  • 交差検証は「前処理も含めてループ内で」実行

3. ランダムシード管理(Controlling Randomness)

問題: 交差検証や推定器の初期化でランダムシードが一貫していないと、結果が再現不可能になる。

from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier

# 同じシードなら同じ結果
rf = RandomForestClassifier(random_state=42, n_estimators=100)
scores1 = cross_val_score(rf, X, y, cv=5)
scores2 = cross_val_score(rf, X, y, cv=5)
# scores1 ≈ scores2

モデル選択の実装パターン(scikit-learn)

GridSearchCV による自動チューニング

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import GradientBoostingClassifier

param_grid = {
    'n_estimators': [50, 100, 200],
    'learning_rate': [0.01, 0.1],
    'max_depth': [3, 5, 7],
    'subsample': [0.8, 1.0]
}

grid_search = GridSearchCV(
    GradientBoostingClassifier(),
    param_grid,
    cv=5,
    scoring='f1',
    n_jobs=-1
)
grid_search.fit(X_train, y_train)

print(f"Best params: {grid_search.best_params_}")
print(f"Best CV score: {grid_search.best_score_}")
best_model = grid_search.best_estimator_
test_score = best_model.score(X_test, y_test)

学習曲線(Learning Curve)による診断

from sklearn.model_selection import learning_curve
import matplotlib.pyplot as plt

train_sizes, train_scores, val_scores = learning_curve(
    LogisticRegression(max_iter=1000),
    X, y,
    cv=5,
    train_sizes=np.linspace(0.1, 1.0, 10),
    n_jobs=-1
)

plt.plot(train_sizes, train_scores.mean(axis=1), label='Training score')
plt.plot(train_sizes, val_scores.mean(axis=1), label='Validation score')
plt.xlabel('Training Set Size')
plt.ylabel('Score')
plt.legend()
plt.show()
  • 両者が収束: モデルが複雑さが不足(underfitting)
  • 訓練 > 検証で乖離: 過学習

まとめ

機械学習は、データ、損失、汎化、評価、モデル選択を一体として扱う設計です。回帰と分類を基礎に、木・アンサンブル・教師なし学習へ広げていくと、実務で何を比較し、どこで失敗しやすいかが見えやすくなります。

参考文献

講義・記事

解説・補助