Machine Learning: KNNとクラスタリング — 最近傍分類と教師なしセグメンテーション

最終更新:2026-08-26

類は友を呼ぶ — KNNは「あなたに最も似た近傍」を見つけて分類し、K-Meansは「最も密なグループ」を見つけてセグメンテーションを行います。

1. 学習内容


2. ユーザー運営マネージャーの実話

(1) 課題:50万人ユーザーへの一律マーケティングは非効率

Bobのプラットフォームには50万人のユーザーがいますが、全員に同じプロモーションメールを送っています。その結果、高価値ユーザーにはプロモーションが物足りず、休眠ユーザーはメールを開かず、全体のコンバージョン率はわずか2%です。ユーザーは多様であり、一律の戦略ではマーケティング予算の80%が浪費されます。

(2) クラスタリングによる解決策

クラスタリングを使えば、ユーザーを高価値・アクティブ・休眠・離脱グループに自動的に分割し、精度の高いマーケティングが可能になります。

PYTHON
from sklearn.cluster import KMeans

# RFM clustering: group users by Recency, Frequency, Monetary
rfm = df[["recency", "frequency", "monetary"]]
kmeans = KMeans(n_clusters=4, random_state=42)
df["segment"] = kmeans.fit_predict(rfm)

for i in range(4):
    segment = df[df["segment"] == i]
    print(f"Segment {i}: {len(segment)} users, "
          f"Avg Monetary={segment['monetary'].mean():.0f} USD")

(3) 成果:精密マーケティングでコンバージョン4倍

Bobがクラスタリングでユーザーを4つのセグメントに分割したところ、高価値グループにはプレミアムオファーを、休眠グループには再活性化パッケージを配信し、全体のコンバージョン率は2%から8%に跳ね上がりました。マーケティングROIは3倍に改善しています。


3. KNN分類

(1) 距離指標とKの選択

▶ サンプル:KNN分類+異なるK値の比較

PYTHON
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np

X, y = load_iris(return_X_y=True)

# Compare different K values
for k in [1, 3, 5, 7, 11, 21]:
    pipe = Pipeline([
        ("scaler", StandardScaler()),
        ("knn", KNeighborsClassifier(n_neighbors=k)),
    ])
    scores = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
    print(f"K={k:2d}: Accuracy={scores.mean():.3f} +/- {scores.std():.3f}")

出力:

TEXT 📖 参照専用
# Executed successfully
距離指標 計算式の特徴 適した用途
ユークリッド距離 直線距離 連続的な特徴量、全次元が同等に重要
マンハッタン距離 ブロック距離 高次元データ、外れ値が多い場合
コサイン距離 角度の距離 テキストベクトル、大きさより方向が重要
ミンコフスキー距離 一般化距離(p=2→ユークリッド、p=1→マンハッタン) 柔軟な調整

(2) Kの影響

K値 決定境界 リスク
K=1 極めて不規則 過学習(ノイズに敏感)
K=小(3〜7) 適度に曲線的 通常は最適
K=大(20以上) ほぼ直線的 学習不足

▶ サンプル:KNNによるユーザー購入予測

PYTHON
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import numpy as np

rng = np.random.default_rng(42)
n = 1000
X = np.column_stack([
    rng.uniform(0, 100, n),   # browsing_time
    rng.uniform(0, 50, n),    # cart_value_usd
    rng.integers(1, 30, n),   # pages_viewed
    rng.integers(0, 10, n),   # previous_purchases
])
y = (X[:, 1] * 0.05 + X[:, 3] * 0.3 + rng.normal(0, 0.5, n) > 2).astype(int)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("knn", KNeighborsClassifier(n_neighbors=7, weights="distance")),
])
pipe.fit(X_train, y_train)
print(classification_report(y_test, pipe.predict(X_test)))

出力:

TEXT 📖 参照専用
# Executed successfully

4. K-Meansクラスタリング

(1) アルゴリズムの流れ

100%
sequenceDiagram
    participant Init as Initialize K Centers
    participant Assign as Assign Points
    participant Update as Update Centers
    participant Check as Converged?

    Init->>Assign: Random K center positions
    loop Until convergence
        Assign->>Update: Each point → nearest center
        Update->>Check: Centers = mean of assigned points
        Check->>Assign: Not converged (centers moved)
    end
    Check-->>Done: Converged! Return clusters

▶ サンプル:K-Meansクラスタリング+エルボー法

PYTHON
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(42)
# Generate 4 natural clusters
X = np.vstack([
    rng.normal([20, 500], [3, 50], (200, 2)),   # High value
    rng.normal([50, 200], [5, 30], (300, 2)),   # Medium
    rng.normal([80, 50], [8, 20], (350, 2)),    # Low
    rng.normal([10, 800], [2, 40], (150, 2)),   # VIP
])

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Elbow method
inertias = []
K_range = range(2, 10)
for k in K_range:
    km = KMeans(n_clusters=k, random_state=42, n_init=10)
    km.fit(X_scaled)
    inertias.append(km.inertia_)

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(K_range, inertias, "bo-", linewidth=2)
ax.set_xlabel("Number of Clusters (K)")
ax.set_ylabel("Inertia (Within-Cluster Sum of Squares)")
ax.set_title("Elbow Method for Optimal K")
ax.axvline(x=4, color="red", linestyle="--", label="Elbow at K=4")
ax.legend()
plt.tight_layout()
plt.savefig("elbow_method.png", dpi=150)

出力:

TEXT 📖 参照専用
# Executed successfully

(2) シルエットスコア

▶ サンプル:シルエットスコアによるKの選択

PYTHON
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler
import numpy as np

# Using same X_scaled from previous example
for k in range(2, 8):
    km = KMeans(n_clusters=k, random_state=42, n_init=10)
    labels = km.fit_predict(X_scaled)
    score = silhouette_score(X_scaled, labels)
    print(f"K={k}: Silhouette Score={score:.3f}")

出力:

TEXT 📖 参照専用
# Executed successfully
指標 意味 最適なKの決め方
Inertia(エルボー法) クラスタ内平方和 エルボーポイントで決定
シルエットスコア 凝集度と分離度のバランス 最大値で決定
Gap Statistic ランダム分布との比較 上限を初めて下回るK

5. DBSCAN密度クラスタリング

▶ サンプル:DBSCANによる任意形状クラスタの発見

PYTHON
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
import numpy as np

rng = np.random.default_rng(42)
# Create non-spherical clusters (two circles)
from sklearn.datasets import make_circles
X, _ = make_circles(n_samples=500, factor=0.5, noise=0.05, random_state=42)

X_scaled = StandardScaler().fit_transform(X)

# K-Means vs DBSCAN
km = KMeans(n_clusters=2, random_state=42, n_init=10)
km_labels = km.fit_predict(X_scaled)

db = DBSCAN(eps=0.3, min_samples=10)
db_labels = db.fit_predict(X_scaled)

n_clusters = len(set(db_labels)) - (1 if -1 in db_labels else 0)
n_noise = list(db_labels).count(-1)

print(f"K-Means Silhouette: {silhouette_score(X_scaled, km_labels):.3f}")
print(f"DBSCAN Clusters: {n_clusters}, Noise points: {n_noise}")
if n_clusters > 1:
    db_valid = db_labels != -1
    print(f"DBSCAN Silhouette: {silhouette_score(X_scaled[db_valid], db_labels[db_valid]):.3f}")

出力:

TEXT 📖 参照専用
# Executed successfully
観点 K-Means DBSCAN
クラスタ形状 球形 任意の形状
K値 事前指定が必要 自動的に発見
ノイズポイント 対応不可 -1としてラベル付け
密度の偏り 苦手 問題なく対応
計算速度 高速 中程度

6. BobのRFMユーザープロファイリングセグメンテーション

▶ サンプル:RFMクラスタリングの完全なプロジェクト

PYTHON
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
n = 5000
df = pd.DataFrame({
    "user_id": range(10001, 10001 + n),
    "recency_days": rng.integers(1, 365, n),
    "frequency": rng.integers(1, 50, n),
    "monetary_usd": rng.exponential(500, n),
})

# RFM features
rfm = df[["recency_days", "frequency", "monetary_usd"]]

# Log transform for skewed features
rfm_log = rfm.copy()
rfm_log["frequency"] = np.log1p(rfm_log["frequency"])
rfm_log["monetary_usd"] = np.log1p(rfm_log["monetary_usd"])

# Scale
scaler = StandardScaler()
rfm_scaled = scaler.fit_transform(rfm_log)

# K-Means with K=4
km = KMeans(n_clusters=4, random_state=42, n_init=10)
df["segment"] = km.fit_predict(rfm_scaled)

# Analyze segments
segment_summary = df.groupby("segment").agg({
    "recency_days": "mean",
    "frequency": "mean",
    "monetary_usd": "mean",
    "user_id": "count",
}).round(1)
segment_summary.columns = ["avg_recency", "avg_frequency", "avg_monetary", "count"]
segment_summary = segment_summary.sort_values("avg_monetary", ascending=False)

# Label segments
labels = ["Champions", "Loyal", "At Risk", "Lost"]
for idx, (_, row) in enumerate(segment_summary.iterrows()):
    print(f"{labels[idx]:10s}: {int(row['count']):5d} users, "
          f"Recency={row['avg_recency']:.0f}d, "
          f"Freq={row['avg_frequency']:.1f}, "
          f"Monetary={row['avg_monetary']:.0f} USD")

出力:

TEXT 📖 参照専用
# Executed successfully
セグメント Recency Frequency Monetary マーケティング戦略
Champions 低(最近もアクティブ) VIPサービス、プレミアムレコメンド
Loyal やや高 ロイヤルティプログラム、クロスセル
At Risk 高(最終購入から長時間経過) 再活性化パッケージ、期間限定オファー
Lost 非常に高 低コストアプローチ、アンケート調査

❓ よくある質問

Q KNNのKとK-MeansのKの違いは何ですか?
A KNNのKは「何人の近傍が投票するか」を意味する分類アルゴリズムのパラメータです。K-MeansのKは「いくつのグループに分けるか」を意味するクラスタリングアルゴリズムのパラメータです。まったく別の概念です。
Q KNNには標準化が必要ですか?
A 必ず必要です。KNNは距離ベースのアルゴリズムなので、スケールが異なる特徴量(例:年収0〜100,000と年齢0〜100)では、大きい値の特徴量が距離計算を完全に支配してしまいます。必ずStandardScalerと組み合わせてください。
Q K-MeansのKはどうやって選べばよいですか?
A エルボー法とシルエットスコアを組み合わせます。エルボー法では変曲点を見つけ、シルエットスコアでは最大値を見つけます。両者が一致しない場合は、ビジネス上の妥当性を優先してください。
Q DBSCANのepsはどうやって選べばよいですか?
A K距離グラフ(k最近傍距離をソートしたもの)をプロットし、エルボーポイントを見つけます。標準化後のepsの典型的な値は0.1〜1.0の範囲です。min_samplesは通常、次元数×2に設定します。
Q クラスタリング結果はどう評価しますか?
A 教師なしクラスタリングには「正解ラベル」がありません。シルエットスコア(数学的評価)とビジネス上の解釈可能性(ドメイン評価)の二重検証を行います。意味のあるビジネスコンセプトに対応するクラスタだけが価値を持ちます。
Q RFMクラスタリングの前にログ変換を適用するのはなぜですか?
A FrequencyとMonetaryは通常、右に歪んだ分布をしており、少数の高価値ユーザーがクラスタ間距離を引き伸ばしてしまいます。ログ変換により分布がより対称に近づき、K-Meansの性能が向上します。

📖 まとめ


📝 練習問題

  1. 基礎(難易度 ⭐):KNNでIrisデータセットを分類してください。K=1, 5, 10, 20の交差検証精度を比較し、最適なKを見つけてください。ヒント:Pipeline(StandardScaler + KNN) + cross_val_score。
  2. 中級(難易度 ⭐⭐):make_blobsで4クラスタのデータを生成してください。K-MeansとDBSCANの両方でクラスタリングし、シルエットスコアとノイズポイントの処理を比較してください。ヒント:DBSCANはノイズを-1としてラベル付けします。
  3. 上級(難易度 ⭐⭐⭐):BobのRFMクラスタリングを実装してください。シミュレーションユーザーデータを生成し、ログ変換+標準化を適用し、エルボー法でKを選択し、K-Meansを実行し、各セグメントに意味のある名前を付け(Champions/Loyal/At Risk/Lost)、セグメントごとの平均RFM値を出力してください。ヒント:第6節の完全なサンプルを参照してください。

← 前へ:SVMサポートベクターマシン | 次へ:特徴量エンジニアリング →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%