Machine Learning: KNNとクラスタリング — 最近傍分類と教師なしセグメンテーション
最終更新:2026-08-26
類は友を呼ぶ — KNNは「あなたに最も似た近傍」を見つけて分類し、K-Meansは「最も密なグループ」を見つけてセグメンテーションを行います。
1. 学習内容
- KNN分類:距離指標(ユークリッド距離・マンハッタン距離・コサイン距離)、Kの選択、投票メカニズム
- K-Meansクラスタリング:アルゴリズムの流れ、Kの選択(エルボー法・シルエットスコア)、収束判定
- DBSCAN密度クラスタリング:コアポイント・ボーダーポイント・ノイズポイント、epsとmin_samplesの調整
- 階層的クラスタリング:AgglomerativeClusteringとデンドログラム
- Bobのユーザープロファイリングセグメンテーション:RFMクラスタリングで高価値・休眠・離脱ユーザーグループを特定
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) アルゴリズムの流れ
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の性能が向上します。
📖 まとめ
- KNN分類:距離指標でK個の最近傍を選び投票する。Kが小さいと過学習、大きいと学習不足になる。標準化は必須
- K-Meansクラスタリング:ポイントの割り当てと中心の更新を反復する。エルボー法とシルエットスコアでKを選択。球形クラスタにのみ有効
- DBSCAN密度クラスタリング:クラスタ数を自動発見し、ノイズをラベル付けし、任意の形状に対応できるが、パラメータに敏感
- RFMユーザーセグメンテーションはEC分野の代表的なクラスタリング応用:Recency+Frequency+Monetary→ユーザープロファイル
- クラスタリング前にログ変換で歪んだ分布を処理し、標準化でスケールの差を解消する
- クラスタ評価=数学的指標(シルエットスコア)+ビジネス上の解釈可能性 — 両方が不可欠
📝 練習問題
- 基礎(難易度 ⭐):KNNでIrisデータセットを分類してください。K=1, 5, 10, 20の交差検証精度を比較し、最適なKを見つけてください。ヒント:Pipeline(StandardScaler + KNN) + cross_val_score。
- 中級(難易度 ⭐⭐):make_blobsで4クラスタのデータを生成してください。K-MeansとDBSCANの両方でクラスタリングし、シルエットスコアとノイズポイントの処理を比較してください。ヒント:DBSCANはノイズを-1としてラベル付けします。
- 上級(難易度 ⭐⭐⭐):BobのRFMクラスタリングを実装してください。シミュレーションユーザーデータを生成し、ログ変換+標準化を適用し、エルボー法でKを選択し、K-Meansを実行し、各セグメントに意味のある名前を付け(Champions/Loyal/At Risk/Lost)、セグメントごとの平均RFM値を出力してください。ヒント:第6節の完全なサンプルを参照してください。