論文・記事に 「ロジスティック回帰」「k-NN」「SVM」「ナイーブベイズ」「判別分析(LDA/QDA)」「One-vs-Rest」 として登場する古典的分類モデル群。 解釈性が高くベースラインとして必須。 テーブルデータでは現代でも常用されます。
論文記事から各用語のリンクをクリックすると、 該当箇所が開きます:
NN/RF が登場する前の古典的分類モデル群。 シンプルで解釈しやすく、 ベースラインや小規模データで未だ強い。
| 手法 | 判定の根拠 | 解釈性 | スケール | 確率出力 |
|---|---|---|---|---|
| ロジスティック回帰 | 線形境界+シグモイド | 高(係数で) | 大規模可 | 自然 |
| k-NN | 近傍 k 個の多数決 | 中 | 推論遅い | 頻度比 |
| SVM | マージン最大の超平面 | 低(カーネル時) | 中規模 | Platt校正 |
| ナイーブベイズ | 条件付き独立仮定 + ベイズ | 中 | 非常に高速 | 自然 |
| LDA / QDA | 各クラスのガウス分布 | 高 | 中規模 | 自然 |
線形結合をシグモイド関数で 0〜1 に押し込み、 確率として解釈:
$$ p(y=1 \mid \boldsymbol{x}) = \sigma(\boldsymbol{w}^\top \boldsymbol{x} + b) = \frac{1}{1 + e^{-(\boldsymbol{w}^\top \boldsymbol{x} + b)}} $$
損失はクロスエントロピー(Log Loss)。 解は最尤推定で求める。 名前は「回帰」だが分類モデル。
🎯 このコードでやること: 分類器を学習。
1 2 3 4 5 | from sklearn.linear_model import LogisticRegression model = LogisticRegression(C=1.0, penalty='l2', max_iter=1000) model.fit(X_train, y_train) print(model.coef_) # 係数 print(model.predict_proba(X_test)[:, 1]) # 陽性確率 |
💬 読み方: 「分類タスクの基本」の典型パターン。 列名や引数を変えると応用可能。
詳細:ロジスティック回帰
「新しい点の近くの $k$ 個の多数決」で分類。 学習フェーズなし(lazy learner)。
$$ \hat{y} = \arg\max_c \sum_{i \in N_k(\boldsymbol{x})} \mathbb{1}\{y_i = c\} $$
$N_k(\boldsymbol{x})$ は $\boldsymbol{x}$ に最近接の $k$ 点。 距離はユークリッド・マンハッタン・コサインなど。
詳細:k近傍法
2クラス間のマージン(境界からのギャップ)を最大化する超平面を学ぶ。
$$ \min_{\boldsymbol{w}, b} \frac{1}{2}\|\boldsymbol{w}\|^2 + C \sum_i \max(0, 1 - y_i(\boldsymbol{w}^\top \boldsymbol{x}_i + b)) $$
$C$ は正則化(小 → マージン重視、 大 → 誤分類重視)。
非線形分類はカーネルトリックで:明示的に高次元に変換せず、 内積をカーネル関数 $K(\boldsymbol{x}_i, \boldsymbol{x}_j)$ で置き換える。 RBF、 多項式、 シグモイドなど。
詳細:SVM
ベイズの定理に基づき、 「特徴量が条件付き独立」と仮定して分類:
$$ p(y \mid \boldsymbol{x}) \propto p(y) \prod_j p(x_j \mid y) $$
独立性仮定は非現実的("naive")だが、 実用的にしばしば良い性能。 とくにテキスト分類(スパム判定、 ニュース分類)で標準。
🎯 このコードでやること: モデルを学習。
1 2 3 | from sklearn.naive_bayes import GaussianNB, MultinomialNB gnb = GaussianNB().fit(X_train, y_train) mnb = MultinomialNB().fit(X_word_counts, y) |
💬 読み方: 「分類タスクの基本」の典型パターン。 列名や引数を変えると応用可能。
各クラスがガウス分布に従うと仮定し、 ベイズ最適境界を導出:
仮定(多変量正規 + 等分散)が成立すれば最適。 違反でもしばしばロバスト。 PCA と数学的関係が深い。
🎯 このコードでやること: モデルを学習。
1 2 3 | from sklearn.discriminant_analysis import LinearDiscriminantAnalysis, QuadraticDiscriminantAnalysis lda = LinearDiscriminantAnalysis().fit(X, y) qda = QuadraticDiscriminantAnalysis().fit(X, y) |
💬 読み方: 「分類タスクの基本」の典型パターン。 列名や引数を変えると応用可能。
2 値分類器を $K$ クラスに拡張する3つの戦略:
| 状況 | 推奨 |
|---|---|
| 解釈性が最優先 | ロジスティック回帰、 決定木 |
| 小サンプル(n < 100) | SVM、 LDA、 ナイーブベイズ |
| 高次元(テキスト等) | 線形 SVM、 Naive Bayes、 ロジスティック |
| 非線形・複雑な境界 | SVM (RBF)、 RF、 GBM、 NN |
| 確率予測が必要 | ロジスティック、 NB、 LDA |
| テキスト分類 | Multinomial NB、 線形 SVM、 BERT |
| テーブルデータ(大規模) | XGBoost、 LightGBM |
| ❌ 誤解 | ✅ 正しい理解 |
|---|---|
| k-NN は標準化不要 | 距離計算が歪むので必須 |
| SVM は常に最強 | 大規模データでは遅い。 確率出力も弱い |
| ナイーブベイズは独立仮定が崩れたら使えない | 理論的には崩れていても実用上しばしば有効 |
| ロジスティック回帰は「回帰」 | 分類モデル。 名前にだまされない |
| k-NN の k は大きいほど良い | バイアス↑になる。 CV で最適 k を選ぶ |
| LDA は線形なので非線形には無力 | 基底変換、 カーネル化、 QDA で対応可 |
k=1。 訓練データの 1 点に完全に従うので、 ノイズまで覚える(高バリアンス)。 k=100 は 100 点の平均で滑らか(高バイアス)。 CV で最適 k を選ぶ。
Multinomial Naive Bayes、 線形 SVM、 ロジスティック回帰(TF-IDF 特徴量 + L1/L2 正則化)。 高次元の単語ベクトルに対して線形モデルが効果的。 現代では BERT/Transformer 系も標準。
「$x_j$ が 1 単位増えると、 ログオッズ $\log(p/(1-p))$ が 1.5 増える」。 オッズ比に変換すれば $e^{1.5} \approx 4.48$。 つまり「$x_j$ が 1 単位増えるとオッズが約 4.5 倍」。
🎯 このコードでやること: SSDSE-B-2026 を読み込み、K-分割交差検証で評価。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import pandas as pd from sklearn.linear_model import LogisticRegression from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.model_selection import cross_val_score df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932') # 前処理 ... X, y を作成 X_std = StandardScaler().fit_transform(X) for name, model in [('Logistic', LogisticRegression()), ('kNN', KNeighborsClassifier(n_neighbors=5)), ('SVM', SVC(kernel='rbf'))]: score = cross_val_score(model, X_std, y, cv=5, scoring='f1').mean() print(f'{name}: F1 = {score:.3f}') |
💬 読み方: skiprows=1 で英語ヘッダ行を飛ばし、 encoding='cp932' で文字化けを回避 / 分割数 K を増やすほど分散は小さいが計算コスト増。
「2 値分類タスク(n=2000、 陽性率 30%)に対し、 ロジスティック回帰・k-NN(k=10)・SVM(RBF) を 5-fold CV で比較。 結果:LR の F1=0.71±0.02、 kNN=0.65±0.04、 SVM=0.74±0.02。 SVM が最良だが、 解釈性を考慮し本番ではロジスティック回帰を採用。 標準化必須のため StandardScaler を Pipeline に組み込んだ。」
SVM・決定木の確率出力は校正されていない(実際の頻度と乖離)。 Platt scaling(シグモイドフィット)や isotonic regression で校正可能。
🎯 このコードでやること: モデルを学習。
1 2 3 | from sklearn.calibration import CalibratedClassifierCV calibrated = CalibratedClassifierCV(base_model, method='sigmoid', cv=5) calibrated.fit(X_train, y_train) |
💬 読み方: 「分類タスクの基本」の典型パターン。 列名や引数を変えると応用可能。
| モデル | クラス | 主要パラメータ | 推奨用途 |
|---|---|---|---|
| ロジスティック回帰 | LogisticRegression | C, penalty, solver, class_weight | 解釈性が重要。 ベースライン |
| k-NN | KNeighborsClassifier | n_neighbors, weights, metric | 小〜中規模、 非線形決定境界 |
| 線形SVM | LinearSVC | C, loss | 高次元疎データ(テキスト等) |
| カーネルSVM | SVC | C, kernel, gamma | 非線形・中規模データ |
| ナイーブベイズ | GaussianNB, MultinomialNB | var_smoothing, alpha | テキスト分類、 ベースライン |
| LDA | LinearDiscriminantAnalysis | solver, shrinkage | クラスが正規・等共分散 |
| QDA | QuadraticDiscriminantAnalysis | reg_param | クラスごとに分散が違うとき |
🎯 このコードでやること: K-分割交差検証で評価、分類器を学習。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.model_selection import GridSearchCV, StratifiedKFold pipe = Pipeline([ ('scaler', StandardScaler()), ('clf', LogisticRegression(max_iter=1000)) ]) param_grid = { 'clf__C': [0.01, 0.1, 1, 10, 100], 'clf__penalty': ['l2'], } cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) grid = GridSearchCV(pipe, param_grid, scoring='f1', cv=cv, n_jobs=-1) grid.fit(X_train, y_train) print('Best params:', grid.best_params_) print('Best CV F1:', grid.best_score_) |
💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増。
初学者向けの目安:
実務の多くは正例率が 1〜10% という不均衡データ。 工夫なしに学習すると「全部負例」と予測してしまう。
class_weight='balanced':損失で少数クラスを重く扱う(まずこれ)🎯 このコードでやること: 分類器を学習。
1 2 3 4 5 6 7 8 9 10 11 | from sklearn.linear_model import LogisticRegression clf = LogisticRegression(class_weight='balanced', max_iter=1000) clf.fit(X_train, y_train) proba = clf.predict_proba(X_val)[:, 1] import numpy as np from sklearn.metrics import f1_score thr_grid = np.linspace(0.05, 0.95, 19) f1s = [f1_score(y_val, proba >= thr) for thr in thr_grid] best_thr = thr_grid[int(np.argmax(f1s))] print('Best threshold:', best_thr, 'F1:', max(f1s)) |
💬 読み方: 「分類タスクの基本」の典型パターン。 列名や引数を変えると応用可能。
StandardScaler 等で標準化した(k-NN・SVM・ロジスティックは必須)論文・教科書でよく登場する関連語句のサブインデックス。
SSDSE-B-2026(都道府県別の社会経済データ)を用いて、 各分類モデルを実際に動かすハンズオン例。 目的変数は「東日本=1(北海道・東北・関東・中部18県)/西日本=0(近畿以西29県)」とし、 特徴量は「総人口」「高齢化率」「製造品出荷額」など 6 変数。
🎯 このコードでやること: SSDSE-B-2026 を読み込み、K-分割交差検証で評価。
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 | import pandas as pd from sklearn.linear_model import LogisticRegression from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC from sklearn.naive_bayes import GaussianNB from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.preprocessing import StandardScaler from sklearn.model_selection import StratifiedKFold, cross_val_score from sklearn.pipeline import Pipeline df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) df.columns = [c.strip() for c in df.columns] # 東日本(コード01-23)= 1、 西日本(24-47)= 0 df['east'] = (df['地域コード'].astype(int) <= 23).astype(int) feats = ['総人口', '15歳未満人口', '65歳以上人口', '製造品出荷額等', '小売業年間商品販売額', '一般病院数'] X = df[feats].astype(float).values y = df['east'].values models = { 'LogisticRegression': LogisticRegression(max_iter=1000), 'kNN(k=5)' : KNeighborsClassifier(n_neighbors=5), 'SVM(RBF)' : SVC(kernel='rbf', C=1.0, gamma='scale'), 'GaussianNB' : GaussianNB(), 'LDA' : LinearDiscriminantAnalysis(), } cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0) for name, clf in models.items(): pipe = Pipeline([('sc', StandardScaler()), ('clf', clf)]) acc = cross_val_score(pipe, X, y, cv=cv, scoring='accuracy').mean() f1 = cross_val_score(pipe, X, y, cv=cv, scoring='f1').mean() print(f'{name:<22s} Acc={acc:.3f} F1={f1:.3f}') |
💬 読み方: skiprows=1 で英語ヘッダ行を飛ばし、 encoding='cp932' で文字化けを回避 / 分割数 K を増やすほど分散は小さいが計算コスト増。
典型的な出力例: LR Acc=0.787 / kNN Acc=0.745 / SVM(RBF) Acc=0.808 / GaussianNB Acc=0.766 / LDA Acc=0.808。 SSDSE-B は n=47 と極小なので、 fold ごとのばらつきが大きい点に注意(標準誤差は ±0.08 程度)。 解釈性が必要なら LR / LDA を、 非線形境界が見たいなら SVM(RBF) を採用するのが定石。
🎯 このコードでやること: K-分割交差検証で評価。
1 2 3 4 5 6 7 8 | from sklearn.model_selection import cross_val_predict from sklearn.metrics import confusion_matrix, classification_report pipe = Pipeline([('sc', StandardScaler()), ('clf', LinearDiscriminantAnalysis())]) y_pred = cross_val_predict(pipe, X, y, cv=cv) print(confusion_matrix(y, y_pred)) print(classification_report(y, y_pred, target_names=['西日本','東日本'])) |
💬 読み方: 分割数 K を増やすほど分散は小さいが計算コスト増。
陽性率が 5% しかないデータで「常に陰性」と予測するだけで Accuracy=0.95 が出てしまう。 これは分類器として無意味なので、 評価指標は F1、 PR-AUC、 Recall など「陽性側を捉えているか」を測るものに切り替える必要がある。 対策として class_weight='balanced'、 SMOTE などのオーバーサンプリング、 閾値調整(decision_function の出力に対する手動カットオフ)を組み合わせる。 不均衡の度合いが極端(陽性率 1% 未満)なら異常検知タスクとして再定義した方がよいケースもある。
k-NN や SVM では特徴量の標準化が必須だが、 全データに対して fit_transform した後で train/test 分割すると、 テストデータの統計量(平均・分散)が訓練時に漏れる「データリーク」が起きる。 結果として CV スコアが楽観的に出るが、 本番デプロイ後に性能が崩れる。 正しい流儀は Pipeline で StandardScaler とモデルを束ね、 CV の各 fold 内で fit を完結させること。 これは sklearn の Pipeline / make_pipeline の最大の存在意義のひとつ。
SVM の predict_proba(Platt scaling 経由)や RF の確率は校正が不十分なことが多い。 そのまま「p>0.5 で陽性」と扱うと、 リスクと利益のバランスが歪む。 医療診断や信用スコアリングのように確率の絶対値が意思決定に直結する場面では CalibratedClassifierCV による sigmoid / isotonic 校正を必ず通す。 校正度の評価には Brier score、 ECE(Expected Calibration Error)、 reliability diagram を用いる。
特徴量次元 d が大きくなると、 任意の 2 点間のユークリッド距離が「ほぼ同じ値」に集中し、 「近い」「遠い」の区別が消失する。 これが次元の呪い(curse of dimensionality)で、 k-NN の精度を急激に劣化させる。 d ≥ 20 程度から目に見えて劣化する。 対策は PCA や UMAP による次元削減、 マンハッタン距離 / コサイン距離への切り替え、 そもそも特徴量選択で d を絞ること。 高次元データでは k-NN を諦めて線形 SVM やロジスティック回帰の方が頑健になる。
係数 β は「他の変数を一定にしたときのログオッズの変化」であり、 単位は特徴量のスケールに依存する。 標準化していない係数同士を「大きさ比較」して「変数 A の方が重要」と結論するのは誤り。 比較するなら標準化済み係数(z-スコア化した特徴量での係数)か、 オッズ比 e^β、 あるいは permutation importance を見る。 また、 多重共線性があると個々の β は不安定(分散が膨らむ)ので VIF も合わせて確認する。
RBFカーネル SVM の性能は C(誤分類のペナルティ)と γ(RBF の幅)の組み合わせに極めて敏感。 既定値(C=1.0, gamma='scale')でも動くが、 ほぼ確実に最適ではない。 必ず GridSearchCV か RandomizedSearchCV で C ∈ {0.01, 0.1, 1, 10, 100}, γ ∈ {0.001, 0.01, 0.1, 1} を対数グリッドで探索する。 探索範囲が境界に当たったら範囲を広げ直す。 さらに、 大規模データでは SVM 自体が O(n²)〜O(n³) で遅いので、 LinearSVC(線形カーネル限定)や SGDClassifier に切り替える判断も必要。
🎯 このコードでやること: 分類器を学習。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.model_selection import GridSearchCV pipe = Pipeline([ ('scaler', StandardScaler()), ('clf' , LogisticRegression(max_iter=1000, solver='liblinear')), ]) param_grid = { 'clf__C' : [0.01, 0.1, 1, 10, 100], 'clf__penalty': ['l1', 'l2'], } gs = GridSearchCV(pipe, param_grid, cv=5, scoring='f1', n_jobs=-1) gs.fit(X, y) print('best params:', gs.best_params_) print('best F1:', gs.best_score_) |
💬 読み方: 「分類タスクの基本」の典型パターン。 列名や引数を変えると応用可能。
scikit-learn は予測重視で、 標準誤差・p値・信頼区間が出ません。 仮説検定や論文表に必要な指標が欲しい場合は statsmodels.api.Logit を使います。
🎯 このコードでやること: モデルを学習。
1 2 3 4 5 6 7 | import statsmodels.api as sm import numpy as np X_design = sm.add_constant(X) # 切片列を追加 model = sm.Logit(y, X_design).fit(disp=False) print(model.summary()) # 係数・SE・z・p値・95%CI が出力 print('Odds ratio:', np.exp(model.params)) |
💬 読み方: 「分類タスクの基本」の典型パターン。 列名や引数を変えると応用可能。
🎯 このコードでやること: 「分類タスクの基本」の最小コード。
1 2 3 4 5 | from scipy import stats for j, name in enumerate(feats): t, p = stats.ttest_ind(X[y==1, j], X[y==0, j], equal_var=False) print(f'{name:<25s} t={t:+.2f} p={p:.4f}') |
💬 読み方: 「分類タスクの基本」の典型パターン。 列名や引数を変えると応用可能。
🎯 このコードでやること: モデルを学習。
1 2 3 4 5 6 7 | from sklearn.calibration import CalibratedClassifierCV from sklearn.svm import SVC base = SVC(kernel='rbf', C=1.0, gamma='scale') cal = CalibratedClassifierCV(base, method='isotonic', cv=5) cal.fit(X, y) proba = cal.predict_proba(X)[:, 1] # 校正済みの確率 |
💬 読み方: 「分類タスクの基本」の典型パターン。 列名や引数を変えると応用可能。
🎯 このコードでやること: 「分類タスクの基本」の最小コード。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import matplotlib.pyplot as plt from sklearn.metrics import roc_curve, auc, precision_recall_curve y_score = cal.predict_proba(X)[:, 1] fpr, tpr, _ = roc_curve(y, y_score) prec, rec, _ = precision_recall_curve(y, y_score) fig, axes = plt.subplots(1, 2, figsize=(11, 4.5)) axes[0].plot(fpr, tpr); axes[0].set_title(f'ROC AUC={auc(fpr,tpr):.3f}') axes[0].plot([0,1],[0,1],'--',color='gray') axes[1].plot(rec, prec); axes[1].set_title('Precision-Recall') for ax in axes: ax.set_xlim(0,1); ax.set_ylim(0,1.02); ax.grid(alpha=.3) plt.tight_layout(); plt.savefig('roc_pr.png', dpi=140) |
💬 読み方: 「分類タスクの基本」の典型パターン。 列名や引数を変えると応用可能。