クラスタリングとは?手法の種類と比較をPythonで実行|k-means・階層型・DBSCAN・ARI/NMIでの評価
クラスタリングとは、正解ラベルのないデータを、似ているもの同士のグループ(クラスタ)に自動で分ける機械学習の手法で、教師なし学習の代表例です。代表的な手法はk-means法・階層的クラスタリング・DBSCAN・混合ガウスモデルで、データの形によって向き不向きがはっきり分かれます。
この記事では、主な手法の違いを一覧で整理したうえで、scikit-learn 1.9.1(2026年9月10日リリース、2026年10月15日確認)で同じデータに6つの手法を当て、結果をARI・NMIという指標で比べます。コードはPython 3.12で実際に実行した出力つきです。k-means法そのものの仕組みはk-means法とは?、教師なし学習全体は教師なし学習とは?で詳しく扱っています。
クラスタリングとは何か(分類との違い)
機械学習の「分類」は、「このメールは迷惑メールか」のように、あらかじめ正解ラベルの付いたデータで学習し、新しいデータにラベルを付けます(教師あり学習)。これに対してクラスタリングは、正解ラベルを使わず、データ同士の距離や密度だけを手がかりにグループを作ります。
| 比較 | 分類(教師あり) | クラスタリング(教師なし) |
|---|---|---|
| 正解ラベル | 必要 | 不要 |
| グループの意味 | 最初から決まっている(迷惑メール/通常など) | 結果を見て人が解釈する |
| 使いどころ | 決まったラベルを自動で付けたい | データにどんなまとまりがあるかを知りたい |
| 例 | 画像の判別、需要の予測 | 顧客のグループ分け、似た文書・商品のまとめ、異常なデータの発見 |
クラスタリングが作るのは「グループ0・1・2」という番号だけです。それぞれのグループが「若い高頻度客」なのか「休眠客」なのかは、グループごとの平均値などを見て、人が意味を付ける必要があります。
まずはk-means法で動かしてみる
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
X, y_true = make_blobs(n_samples=300, centers=3, cluster_std=0.8, random_state=0)
km = KMeans(n_clusters=3, random_state=0)
labels = km.fit_predict(X)
print(X[:3].round(2))
print(labels[:10])
print(km.cluster_centers_.round(2))
実行結果:
[[ 0.25 5.2 ]
[ 1.47 -0.21]
[ 1.59 4.4 ]]
[2 1 2 0 0 0 2 2 0 2]
[[-1.72 2.81]
[ 1.93 0.8 ]
[ 0.9 4.33]]
2列の数値データ300件を、3つのグループに分けました。labelsが各データの所属グループの番号、cluster_centers_が各グループの中心です。番号そのものに意味はなく、実行条件が変わると0と2が入れ替わることもあります。
クラスタリングの主な手法と種類
手法は、グループの作り方によって大きく4つに分けられます。
| 種類 | 代表的な手法 | 考え方 | グループ数の指定 | 得意・不得意 |
|---|---|---|---|---|
| 分割型(重心ベース) | k-means法、k-medoids法 | k個の中心を置き、各データを一番近い中心に割り当てる | 必要 | 丸くて大きさのそろったグループに強い。細長い・曲がった形は苦手 |
| 階層型 | 凝集型階層クラスタリング(ward法・最短距離法など) | 近いもの同士を順に併合し、樹形図(デンドログラム)を作る | 途中で切る位置を決める | グループの入れ子構造が見える。データが多いと計算が重い |
| 密度ベース | DBSCAN、HDBSCAN、OPTICS | データが密に集まった領域をグループとし、まばらな点はノイズにする | 不要 | 任意の形を見つけられ、外れ値をノイズとして分けられる。密度の差が大きいと調整が難しい |
| モデルベース | 混合ガウスモデル(GMM) | データが複数の正規分布の重ね合わせから生まれたと考える | 必要 | 楕円形のグループや「どのグループに属する確率か」が欲しいときに向く |
scikit-learnの公式ユーザーガイドにも、手法ごとのパラメータ・計算量・向いている形をまとめた比較表があります(scikit-learn:Clustering)。HDBSCANはscikit-learn 1.3で追加された手法で、DBSCANと違って密度の異なるグループも扱え、パラメータ選びに強いとされています(scikit-learn:HDBSCAN)。
6つの手法を同じデータで比較する
丸い3つの塊(blobs)と、2つの三日月が組み合わさった形(moons)の2種類のデータで、6つの手法を比べました。どちらも正解のグループが分かっている練習用データなので、結果が正解とどれだけ一致するかをARIとNMIで測れます。
import numpy as np
from sklearn.datasets import make_blobs, make_moons
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN, HDBSCAN
from sklearn.mixture import GaussianMixture
from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score
datasets = {
"blobs": make_blobs(n_samples=300, centers=3, cluster_std=0.8, random_state=0),
"moons": make_moons(n_samples=300, noise=0.06, random_state=0),
}
models = {
"k-means": lambda k: KMeans(n_clusters=k, random_state=0),
"階層(ward)": lambda k: AgglomerativeClustering(n_clusters=k, linkage="ward"),
"階層(single)": lambda k: AgglomerativeClustering(n_clusters=k, linkage="single"),
"GMM": lambda k: GaussianMixture(n_components=k, random_state=0),
"DBSCAN": lambda k: DBSCAN(eps=0.3, min_samples=5),
"HDBSCAN": lambda k: HDBSCAN(min_cluster_size=15, copy=True),
}
for name, (X, y) in datasets.items():
X = StandardScaler().fit_transform(X)
k = len(np.unique(y))
print(f"--- {name} (正解のグループ数 {k})")
for mname, make in models.items():
labels = make(k).fit_predict(X)
n = len(set(labels) - {-1})
noise = int((labels == -1).sum())
print(f"{mname:12} グループ数{n} ノイズ{noise:3d} ARI {adjusted_rand_score(y, labels):.2f} NMI {normalized_mutual_info_score(y, labels):.2f}")
実行結果:
--- blobs (正解のグループ数 3)
k-means グループ数3 ノイズ 0 ARI 0.94 NMI 0.92
階層(ward) グループ数3 ノイズ 0 ARI 0.93 NMI 0.90
階層(single) グループ数3 ノイズ 0 ARI 0.00 NMI 0.01
GMM グループ数3 ノイズ 0 ARI 0.90 NMI 0.88
DBSCAN グループ数3 ノイズ 25 ARI 0.48 NMI 0.57
HDBSCAN グループ数3 ノイズ105 ARI 0.47 NMI 0.59
--- moons (正解のグループ数 2)
k-means グループ数2 ノイズ 0 ARI 0.47 NMI 0.37
階層(ward) グループ数2 ノイズ 0 ARI 0.21 NMI 0.16
階層(single) グループ数2 ノイズ 0 ARI 1.00 NMI 1.00
GMM グループ数2 ノイズ 0 ARI 0.48 NMI 0.38
DBSCAN グループ数2 ノイズ 1 ARI 0.99 NMI 0.99
HDBSCAN グループ数2 ノイズ 5 ARI 0.97 NMI 0.93
結果は、データの形によってきれいに逆転しました。
- 丸い塊(blobs):k-means・ward法・GMMがほぼ正解(ARI 0.9以上)。最短距離法(single)は、塊どうしをつなぐ点をたどって1つにまとめてしまい、ARIは0.00でした。DBSCAN・HDBSCANは塊の外側の点をノイズに回したぶん、スコアが下がっています。
- 三日月(moons):k-means・ward法・GMMは「丸いグループ」を前提にしているため、三日月を途中で切ってしまいARIは0.5以下。逆に最短距離法・DBSCAN・HDBSCANはほぼ正解しました。
「どの手法が一番優れているか」ではなく、「データのまとまりがどんな形をしているか」で選ぶ、というのがクラスタリングの手法選びの基本です。なお、DBSCANのepsやHDBSCANのmin_cluster_sizeの値を変えると結果も変わります。ここでは各手法のパラメータを1つに固定して比べています。
クラスタリングの評価方法(ARI・NMI・シルエット係数)
評価の指標は、正解ラベルがあるときに使う「外部指標」と、正解がなくても使える「内部指標」に分かれます。
| 指標 | 種類 | 範囲と読み方 |
|---|---|---|
| ARI(調整ランド指数) | 外部指標 | -0.5〜1.0。1.0で完全一致、でたらめな割り当てでは0.0付近 |
| NMI(正規化相互情報量) | 外部指標 | 0〜1。1で完全一致 |
| シルエット係数 | 内部指標 | -1〜1。1に近いほどグループ内がまとまり、他のグループと離れている。0付近はグループが重なっている |
出典:scikit-learn:adjusted_rand_score、scikit-learn:silhouette_score
ARIとNMIはどちらも、グループの番号の付け方が違っても(正解の0が結果では2になっていても)正しく一致を測れます。実務では正解ラベルがないことがほとんどなので、ARI・NMIは手法の比較や練習用に使い、本番ではシルエット係数などの内部指標と、グループの中身を人が見て解釈することを組み合わせます。
グループ数(k)の決め方:エルボー法とシルエット分析
k-meansやGMMではグループ数を指定する必要があります。4つの塊を持つデータで、kを2〜7まで変えて2つの指標を見てみます。
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
X, _ = make_blobs(n_samples=300, centers=4, cluster_std=0.9, random_state=1)
for k in range(2, 8):
km = KMeans(n_clusters=k, random_state=0).fit(X)
print(f"k={k} 慣性(SSE) {km.inertia_:8.1f} シルエット {silhouette_score(X, km.labels_):.3f}")
実行結果:
k=2 慣性(SSE) 2097.3 シルエット 0.715
k=3 慣性(SSE) 1008.5 シルエット 0.606
k=4 慣性(SSE) 474.1 シルエット 0.661
k=5 慣性(SSE) 424.6 シルエット 0.596
k=6 慣性(SSE) 381.2 シルエット 0.543
k=7 慣性(SSE) 340.2 シルエット 0.459
慣性(各点と中心の距離の二乗の合計)は、k=4までは大きく下がり、そこから下がり方が急に鈍くなっています。この曲がり角(肘)を選ぶのがエルボー法で、ここではk=4が選ばれます。一方、シルエット係数はk=2で最大になりました。近い位置にある塊どうしを1つに見なすと、それはそれで「よくまとまった2グループ」に見えるためです。
このように、指標によって答えが変わることは珍しくありません。複数の指標を見たうえで、最後は「そのグループ分けで、目的(施策の出し分けなど)に使える違いが出ているか」で決めるのが現実的です。
前処理:標準化しないと結果が変わる
距離を使う手法では、数値の大きさ(単位)が違う列があると、大きい列だけで結果が決まってしまいます。年収(数百)と年齢(数十)を使った例です。
import numpy as np
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
rng = np.random.default_rng(0)
# 年収(万円)と年齢:2つのグループを、年齢の違いで作る
age = np.r_[rng.normal(25, 2, 50), rng.normal(50, 2, 50)]
income = rng.normal(500, 150, 100)
X = np.c_[income, age]
truth = np.r_[np.zeros(50), np.ones(50)]
for name, data in [("そのまま", X), ("標準化あり", StandardScaler().fit_transform(X))]:
labels = KMeans(n_clusters=2, random_state=0).fit_predict(data)
acc = max((labels == truth).mean(), (labels != truth).mean())
print(f"{name}: 年齢グループとの一致率 {acc:.2f}")
実行結果:
そのまま: 年齢グループとの一致率 0.52
標準化あり: 年齢グループとの一致率 1.00
このデータは、年齢が25歳前後と50歳前後の2グループに分かれるように作ってあります。標準化しないと、ばらつきの大きい年収の値だけでグループが分けられ、年齢の違いはほとんど反映されませんでした(一致率0.52は、でたらめとほぼ同じ)。StandardScalerで各列を平均0・標準偏差1にそろえると、年齢の2グループを正しく分けられています。標準化の考え方は正規化と標準化の違いで詳しく説明しています。
クラスタリングの活用例と注意点
| 分野 | 使い方の例 |
|---|---|
| マーケティング | 購買回数・金額・最終購入日などで顧客をグループに分け、グループごとに案内を変える(RFM分析と組み合わせることが多い) |
| 文書・商品 | 文章や商品説明をベクトルにして、似た内容のものをまとめる |
| 画像 | 画素の色をk-meansでまとめて色数を減らす(減色) |
| 異常の発見 | DBSCANなどで、どのグループにも入らない点をノイズとして取り出す |
注意したいのは、クラスタリングは「データにグループがなくても、指定した数のグループを作ってしまう」点です。k-meansにk=5と指定すれば、本当はまとまりのないデータでも5つに分けます。結果のグループに意味があるかどうかは、グループごとの特徴を見て確かめる必要があります。顧客の分析での使い方はRFM分析とは?も参考になります。
よくある質問
Q. クラスタリングとは何ですか?
正解ラベルのないデータを、似ているもの同士のグループ(クラスタ)に自動で分ける機械学習の手法です。教師なし学習の代表例で、顧客のグループ分けや似た文書のまとめなどに使われます。
Q. クラスタリングと分類の違いは?
分類は正解ラベル付きのデータで学習してラベルを予測する教師あり学習です。クラスタリングは正解ラベルを使わずにグループを作り、各グループの意味は結果を見て人が解釈します。
Q. クラスタリングの手法にはどんな種類がありますか?
k-means法などの分割型、樹形図を作る階層型、DBSCAN・HDBSCANなどの密度ベース、混合ガウスモデルなどのモデルベースの4つに大きく分けられます。丸い塊にはk-means、曲がった形やノイズを含むデータには密度ベースが向きます。
Q. ARIとNMIの違いは?
どちらも正解ラベルとクラスタリング結果の一致度を測る外部指標です。ARIは-0.5〜1.0ででたらめなら0付近、NMIは0〜1で、どちらも1が完全一致です。正解がないときはシルエット係数などの内部指標を使います。
Q. クラスタ数はどう決めればよいですか?
エルボー法やシルエット係数でkの候補を絞り、最後はグループ分けが目的に使えるかで決めます。指標によって答えが変わることがあるため、1つの指標だけで決めないことが大切です。
まとめ
クラスタリングは、正解ラベルのないデータを似たもの同士のグループに分ける教師なし学習の手法です。同じデータに6つの手法を当てた比較では、丸い塊にはk-meansやward法、三日月のような曲がった形にはDBSCAN・HDBSCAN・最短距離法が向き、結果ははっきり逆転しました。手法はデータの形で選び、標準化などの前処理を忘れず、グループ数や結果の意味は複数の指標と人の目で確かめてください。
機械学習の全体像は機械学習とは?仕組み・種類・できることを簡単に解説|Pythonで動く例つきにまとめています。
クラスタリングは、テックジムのAIエンジニア養成コースの第1章「教師なし学習と自然言語処理入門」で扱っています。機械学習とディープラーニングを16章で順に学ぶコースで、章ごとに実務での使いどころ(想定案件)が示されています。





