機械学習の全体地図(7)Pythonで書く
scikit-learnを中心に、すべての推定器に共通の使い方と、回帰・分類・クラスタリング・異常検知などのコードのレシピ、地図の項目とPythonのクラスの対応表をまとめます。
連載 機械学習の全体地図 第7回/全8回
Pythonで書く
地図の各項目を、Pythonの機械学習ライブラリ scikit-learn で使う方法です。scikit-learn 1.5以上が必要で、下のコードはすべて 1.8 で実行して確認しています。バージョンは import sklearn; print(sklearn.__version__) で確認できます。
すべての推定器に共通の使い方
scikit-learn では、モデルや前処理の部品を「推定器」(Estimator)と呼び、どれも同じ3つの操作で使えます。fit で学習し、predict で予測し、前処理なら transform で変換します。教師ありは fit(X, y)、教師なしは fit(X) と、①学び方の違いがそのまま引数の違いになっています。
レシピ
そのままコピーして動かせます。データは scikit-learn に同梱のものか、コード内で作るものなので、ダウンロードは要りません。出力の数値はバージョンや環境によって少し変わることがあります。
基本の型:分類を最後まで
④手順の「分割→前処理→学習→評価」を最小限で書いた型です。前処理とモデルを Pipeline(1つにまとめる仕組み)でまとめると、標準化の平均などが学習用データだけで計算され、データリークを防げます。 地図の該当箇所へ
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, classification_report
# 乳がん診断データ(0=悪性, 1=良性)
X, y = load_breast_cancer(return_X_y=True)
# 最初にテスト用を取り分ける。stratify でクラスの比率を保つ
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=0)
# 前処理とモデルを1つにまとめる
model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
model.fit(X_train, y_train)
# テスト用データで1回だけ評価する
pred = model.predict(X_test)
print(confusion_matrix(y_test, pred)) # [[TN, FP], [FN, TP]] の並び
print(classification_report(y_test, pred, digits=3))出力例
[[40 2]
[ 0 72]]
precision recall f1-score support
0 1.000 0.952 0.976 42
1 0.973 1.000 0.986 72
accuracy 0.982 114
macro avg 0.986 0.976 0.981 114
weighted avg 0.983 0.982 0.982 114回帰:ベースラインと比べる
まず「平均値を返すだけ」のベースラインを作り、それを超えるかで判断します。このデータ(約440件)では線形モデルが勾配ブースティングに勝ちます。表形式のデータでも、件数が少ないと単純なモデルが強いことがあるため、複数を比べるのが基本です。 地図の該当箇所へ
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.dummy import DummyRegressor
from sklearn.linear_model import Ridge
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.metrics import mean_absolute_error, root_mean_squared_error, r2_score
X, y = load_diabetes(return_X_y=True) # 1年後の病気の進行度(数値)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
models = {
"ベースライン(平均値)": DummyRegressor(),
"線形(Ridge)": Ridge(),
"勾配ブースティング": HistGradientBoostingRegressor(max_iter=200, learning_rate=0.05, random_state=0),
}
for name, m in models.items():
pred = m.fit(X_train, y_train).predict(X_test)
print(f"{name}: MAE={mean_absolute_error(y_test, pred):.1f} "
f"RMSE={root_mean_squared_error(y_test, pred):.1f} R2={r2_score(y_test, pred):.3f}")出力例
ベースライン(平均値): MAE=59.2 RMSE=71.7 R2=-0.001 線形(Ridge): MAE=46.6 RMSE=58.1 R2=0.341 勾配ブースティング: MAE=50.7 RMSE=65.8 R2=0.155
表データの前処理:列ごとに処理を分ける
数値列とカテゴリ列で別々の前処理を ColumnTransformer でまとめます。欠損値や、学習時になかったカテゴリが来ても止まりません(書き方を示すための8行だけのデータです)。 地図の該当箇所へ
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import make_pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression
df = pd.DataFrame({
"年齢": [25, 32, np.nan, 51, 46, 29, 38, np.nan],
"月額": [3000, 5000, 4500, np.nan, 8000, 3500, 6000, 7000],
"プラン": ["A", "B", "B", "C", "C", "A", None, "B"],
"解約": [1, 0, 0, 1, 0, 1, 0, 0],
})
X, y = df.drop(columns="解約"), df["解約"]
# 数値列: 中央値で補完+欠損フラグ → 標準化
num = make_pipeline(SimpleImputer(strategy="median", add_indicator=True), StandardScaler())
# カテゴリ列: 最頻値で補完 → One-hot(未知のカテゴリは無視)
cat = make_pipeline(SimpleImputer(strategy="most_frequent"), OneHotEncoder(handle_unknown="ignore"))
pre = ColumnTransformer([("num", num, ["年齢", "月額"]), ("cat", cat, ["プラン"])])
model = make_pipeline(pre, LogisticRegression()).fit(X, y)
print(pre.get_feature_names_out())
new = pd.DataFrame({"年齢": [40], "月額": [np.nan], "プラン": ["D"]}) # 欠損と未知のカテゴリ
print("解約確率:", model.predict_proba(new)[:, 1].round(3))出力例
['num__年齢' 'num__月額' 'num__missingindicator_年齢' 'num__missingindicator_月額' 'cat__プラン_A' 'cat__プラン_B' 'cat__プラン_C'] 解約確率: [0.802]
交差検証:データに合った分割を選ぶ
評価の分割方法はデータの性質で選びます。時系列分割では、学習用が常に検証用より過去になることを確認できます。 地図の該当箇所へ
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import cross_val_score, StratifiedKFold, TimeSeriesSplit
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
X, y = load_breast_cancer(return_X_y=True)
model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
# 分類: 層化k分割(各分割でクラス比率をそろえる)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
scores = cross_val_score(model, X, y, cv=cv, scoring="f1")
print("F1:", scores.round(3), "平均", scores.mean().round(3))
# 時系列: 常に過去で学び、直後で検証する(シャッフルしない)
for train_idx, test_idx in TimeSeriesSplit(n_splits=3).split(np.arange(12)):
print("学習", train_idx, "→ 検証", test_idx)
# 同じ顧客の行が複数ある場合は GroupKFold を使い、
# cross_val_score(model, X, y, cv=GroupKFold(5), groups=顧客ID) とする出力例
F1: [0.966 0.979 0.986 1. 0.986] 平均 0.983 学習 [0 1 2] → 検証 [3 4 5] 学習 [0 1 2 3 4 5] → 検証 [6 7 8] 学習 [0 1 2 3 4 5 6 7 8] → 検証 [ 9 10 11]
ハイパーパラメータ探索
Pipeline の中の設定は「部品名__設定名」で指定します。探索は学習用データの中の交差検証で行い、テスト用データは最後まで使いません。 地図の該当箇所へ
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
param = {"logisticregression__C": [0.01, 0.1, 1, 10, 100]} # 正則化の強さ(小さいほど強い)
search = GridSearchCV(model, param, cv=5, scoring="f1").fit(X_train, y_train)
print("最良の設定:", search.best_params_, "交差検証F1:", round(search.best_score_, 3))
print("テストF1(最後に1回):", round(search.score(X_test, y_test), 3))出力例
最良の設定: {'logisticregression__C': 0.1} 交差検証F1: 0.978
テストF1(最後に1回): 0.973偏ったデータ:クラスの重みと閾値
陽性が5%のデータです。重みなしでは正解率94%でも再現率は2%で、陽性をほぼ見逃しています。クラスの重み(少数派の誤りを重く数える設定)を付けると再現率は75%に上がり、代わりに適合率と正解率が下がります。PR-AUCはむしろ少し下がっています(0.25→0.21)。重み付けは主に「判定の境目」を動かすもので、陽性を見分ける力そのものを高めるわけではないためです。 地図の該当箇所へ
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, TunedThresholdClassifierCV
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, precision_score, recall_score, average_precision_score
X, y = make_classification(n_samples=3000, weights=[0.95], class_sep=0.5,
flip_y=0.02, random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=0)
for cw in [None, "balanced"]:
m = LogisticRegression(class_weight=cw).fit(X_train, y_train)
p = m.predict(X_test)
print(f"class_weight={cw}: 正解率={accuracy_score(y_test, p):.3f} "
f"適合率={precision_score(y_test, p):.3f} 再現率={recall_score(y_test, p):.3f} "
f"PR-AUC={average_precision_score(y_test, m.predict_proba(X_test)[:, 1]):.3f}")
# F1が最大になる閾値を交差検証で選ぶ(scikit-learn 1.5以上)
tuned = TunedThresholdClassifierCV(LogisticRegression(), scoring="f1").fit(X_train, y_train)
print("選ばれた閾値:", round(tuned.best_threshold_, 3))出力例
class_weight=None: 正解率=0.941 適合率=0.500 再現率=0.023 PR-AUC=0.250 class_weight=balanced: 正解率=0.684 適合率=0.127 再現率=0.750 PR-AUC=0.205 選ばれた閾値: 0.11
クラスタリングと次元削減
正解ラベルを使わずにグループ分けします。シルエット係数が最大のグループ数が、本当のグループ数と一致するとは限らない点に注意してください。 地図の該当箇所へ
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score, adjusted_rand_score
X, y = load_iris(return_X_y=True) # アヤメ3品種。y はクラスタリングには使わない
Xs = StandardScaler().fit_transform(X)
for k in [2, 3, 4, 5]:
labels = KMeans(n_clusters=k, n_init=10, random_state=0).fit_predict(Xs)
print(f"k={k}: シルエット={silhouette_score(Xs, labels):.3f} "
f"正解との一致ARI={adjusted_rand_score(y, labels):.3f}")
pca = PCA(n_components=2).fit(Xs)
print("2次元に圧縮して残る情報の割合:", pca.explained_variance_ratio_.sum().round(3))出力例
k=2: シルエット=0.582 正解との一致ARI=0.568 k=3: シルエット=0.460 正解との一致ARI=0.620 k=4: シルエット=0.387 正解との一致ARI=0.473 k=5: シルエット=0.346 正解との一致ARI=0.420 2次元に圧縮して残る情報の割合: 0.958
異常検知
正常データの中に混ぜた珍しい点を、ラベルなしで見つけます。predict は 1=正常、−1=異常を返します。 地図の該当箇所へ
import numpy as np
from sklearn.ensemble import IsolationForest
rng = np.random.default_rng(0)
normal = rng.normal(0, 1, size=(300, 2)) # 正常データ
odd = rng.uniform(4, 6, size=(10, 2)) # 珍しいデータ
X = np.vstack([normal, odd])
is_odd = np.r_[np.zeros(300), np.ones(10)]
iso = IsolationForest(contamination=0.03, random_state=0).fit(X)
flag = iso.predict(X) == -1 # 異常と判定したもの
print("異常と判定:", flag.sum(), "件")
print("そのうち本当に珍しいデータ:", int((flag & (is_odd == 1)).sum()), "/ 10件")出力例
異常と判定: 9 件 そのうち本当に珍しいデータ: 8 / 10件
予測の根拠を確かめる
ある特徴量をシャッフルしたときに精度がどれだけ落ちるかで、その特徴量の重要さを測ります(Permutation Importance)。このデータのように似た特徴量が多いと、1つを並べ替えて無効にしても他が補うため、値が小さく出ます。 地図の該当箇所へ
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.inspection import permutation_importance
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
data.data, data.target, test_size=0.2, stratify=data.target, random_state=0)
model = RandomForestClassifier(random_state=0).fit(X_train, y_train)
r = permutation_importance(model, X_test, y_test, n_repeats=10, random_state=0)
for i in r.importances_mean.argsort()[::-1][:5]:
print(f"{data.feature_names[i]:<25} {r.importances_mean[i]:.4f}")出力例
area error 0.0061 worst radius 0.0035 mean texture 0.0026 worst texture 0.0026 worst area 0.0026
過学習を目で確かめる
決定木の深さを変えながら、学習データと検証データの精度を比べます。学習精度は上がり続けても、検証精度はどこかで頭打ちになります。 地図の該当箇所へ
from sklearn.datasets import make_classification
from sklearn.model_selection import validation_curve
from sklearn.tree import DecisionTreeClassifier
X, y = make_classification(n_samples=500, n_features=20, flip_y=0.15, random_state=0)
depths = [1, 2, 3, 5, 8, 12, None]
train, valid = validation_curve(DecisionTreeClassifier(random_state=0), X, y,
param_name="max_depth", param_range=depths, cv=5)
for d, a, b in zip(depths, train.mean(1), valid.mean(1)):
print(f"深さ={str(d):>4}: 学習={a:.3f} 検証={b:.3f}")出力例
深さ= 1: 学習=0.799 検証=0.784 深さ= 2: 学習=0.814 検証=0.792 深さ= 3: 学習=0.855 検証=0.836 深さ= 5: 学習=0.919 検証=0.822 深さ= 8: 学習=0.978 検証=0.798 深さ= 12: 学習=0.998 検証=0.780 深さ=None: 学習=1.000 検証=0.784
対応表:地図の項目とPythonのクラス
「—」は scikit-learn に該当する機能がないものです。追加ライブラリは pip install で入れて使います。
① 学び方
| 項目 | scikit-learn | 追加ライブラリ |
|---|---|---|
| 教師あり Supervised learning | fit(X, y) | — |
| 教師なし Unsupervised learning | fit(X) | — |
| 半教師あり Semi-supervised learning | SelfTrainingClassifier、LabelSpreading | — |
| 自己教師あり Self-supervised learning | — | PyTorch、Hugging Face Transformers |
| 強化学習 Reinforcement learning | — | Gymnasium、Stable-Baselines3 |
| オンライン学習 Online learning | SGDClassifier.partial_fit | — |
| 転移学習・ファインチューニング Transfer learning / Fine-tuning | — | PyTorch、Hugging Face Transformers |
② タスク
| 項目 | scikit-learn | 追加ライブラリ |
|---|---|---|
| 回帰 Regression | LinearRegression、HistGradientBoostingRegressor | — |
| 分類 Classification | LogisticRegression、HistGradientBoostingClassifier | — |
| マルチラベル分類 Multi-label classification | OneVsRestClassifier、MultiOutputClassifier | — |
| 順序回帰 Ordinal regression | — | statsmodels(OrderedModel) |
| ランキング Learning to rank | — | LightGBM(LGBMRanker) |
| 系列ラベリング Sequence labeling | — | sklearn-crfsuite、Transformers |
| 時系列予測 Time series forecasting | TimeSeriesSplit | statsmodels(ARIMA) |
| 生存時間分析 Survival analysis | — | lifelines、scikit-survival |
| クラスタリング Clustering | KMeans、AgglomerativeClustering、DBSCAN、HDBSCAN、GaussianMixture | — |
| 次元削減 Dimensionality reduction | PCA、TSNE | umap-learn |
| 異常検知 Anomaly detection | IsolationForest、OneClassSVM、LocalOutlierFactor | — |
| 密度推定 Density estimation | KernelDensity、GaussianMixture | — |
| 相関ルール Association rule mining | — | mlxtend(apriori) |
| 生成 Generative modeling | GaussianMixture.sample | PyTorch、diffusers |
③ モデル
| 項目 | scikit-learn | 追加ライブラリ |
|---|---|---|
| 線形モデル Linear models | LinearRegression、Ridge、Lasso、LogisticRegression、LinearSVC | statsmodels(ARIMA) |
| カーネル法 Kernel methods | SVC、SVR、GaussianProcessRegressor | — |
| 決定木・アンサンブル Trees / Ensembles | DecisionTreeClassifier、RandomForestClassifier、HistGradientBoostingClassifier | LightGBM、XGBoost、CatBoost |
| 近傍ベース Nearest neighbors | KNeighborsClassifier | — |
| 確率モデル Probabilistic models | GaussianNB、MultinomialNB、GaussianMixture | hmmlearn(隠れマルコフ) |
| ニューラルネット Neural networks | MLPClassifier | PyTorch |
| アンサンブル全般 Ensembles | BaggingClassifier、StackingClassifier、VotingClassifier | — |
④ 手順・評価
| 項目 | scikit-learn | 追加ライブラリ |
|---|---|---|
| 欠損値 Missing values | SimpleImputer、KNNImputer、IterativeImputer | — |
| 尺度の変換 Scaling | StandardScaler、MinMaxScaler | — |
| カテゴリ変数 Categorical encoding | OneHotEncoder、OrdinalEncoder、TargetEncoder | — |
| テキスト Text features | TfidfVectorizer | fugashi・Janome(日本語の分かち書き) |
| クラスの偏り Class imbalance | class_weight、TunedThresholdClassifierCV | imbalanced-learn(SMOTE) |
| 列ごとの前処理 Column-wise preprocessing | ColumnTransformer、Pipeline | — |
| データ分割 Data splitting | train_test_split、StratifiedKFold、GroupKFold、TimeSeriesSplit、LeaveOneOut | — |
| ハイパーパラメータ探索 Hyperparameter tuning | GridSearchCV、RandomizedSearchCV | Optuna |
| 予測根拠 Model explanation | permutation_importance、PartialDependenceDisplay | SHAP |
| 評価指標 Metrics | sklearn.metrics | torchmetrics(mAP・FID)、sacrebleu(BLEU) |
Spark