← 技術ノート一覧

MACHINE LEARNING · CLUSTERING

k-meansを、重心が動くところから理解する

点を近い重心へ分け、各グループの平均へ重心を動かす。その二つだけで、まとまりが生まれる過程を見ます。

一次情報の確認日:2026年8月31日

01

重心が落ち着くまでを見る

Stepで「割り当て→平均への移動」を1回進めます。背景色は現在の重心に最も近い領域です。

反復
0
inertia
1,085,095
点
30

キャンバスを押すと点を追加できます。細長いデータへ切り替えると、k-meansの苦手な形が見えます。

02

目的は、点と所属重心の二乗距離を小さくすること

k-meansは、各点を最も近い重心へ割り当て、その点群の平均へ重心を更新します。画面のinertiaは、各点から所属重心までの二乗距離の合計です。

更新を繰り返すとinertiaは下がり、やがて重心が動かなくなります。ただし得られるのは局所解で、最初の重心の置き方によって結果が変わることがあります。

03

丸い塊には強い。細長い形や密度差には弱い

最も近い重心で空間を分けるため、凸で似た大きさの塊を想定した動きになります。細長い群、輪、密度差の大きい群では、人が期待するまとまりと一致しないことがあります。

実務では複数の初期値を試し、スケーリングを確認し、silhouetteなど別の指標やDBSCAN・Gaussian mixtureなどの候補とも比較します。kを決め打ちして終わらないことが重要です。

04

アルゴリズムの芯だけを書く

2次元点を対象に、割り当てと平均更新をそのままPythonへ写した学習用コードです。

Python 3kmeans_from_scratch.py
def kmeans(points, k, iterations=20):
    """A compact teaching implementation for 2D points."""
    centroids = [list(point) for point in points[:k]]

    for _ in range(iterations):
        groups = [[] for _ in range(k)]

        for point in points:
            label = min(
                range(k),
                key=lambda i: squared_distance(point, centroids[i]),
            )
            groups[label].append(point)

        updated = []
        for i, group in enumerate(groups):
            if not group:
                updated.append(centroids[i])
                continue
            updated.append([
                sum(p[0] for p in group) / len(group),
                sum(p[1] for p in group) / len(group),
            ])

        if all(squared_distance(a, b) < 1e-10
               for a, b in zip(centroids, updated)):
            break
        centroids = updated

    return centroids, groups

def squared_distance(a, b):
    return (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2

05

このサンプルをそのまま本番へ持ち込まない理由

06

確認した一次情報

  1. scikit-learn User Guide — K-means

    目的関数、反復手順、初期値依存、形状上の制約を確認。

  2. scikit-learn API — sklearn.cluster.KMeans

    実務用APIの主要パラメータと出力を確認。

  3. Lloyd (1982), Least Squares Quantization in PCM

    反復量子化手順の原典を確認。