Pythonでクラスタ分析 : k-means

Scikit-learnのk-meansを使ってみました。
    目次
  • k-means法とは
  • k-meansの難点
  • 実行例
  • エルボー法
  • 使ってみた感想

k-means法とは

データ群にクラスタ中心を置いて、ちょどよくなるまでその中心を動かしていく方法です。
なので、クラスタ数や初期化の方法、計算回数上限なんかが引数になってくるんですね。

k-meansの難点

k-meansはいわゆる教師なし学習になりますので、そもそもクラスタがいくつあるのか正解がわかりません。計算するときにクラスタ数を指定しなければいけないのですがいくつが妥当なのかわからない状態で行います。

そこでエルボー法といって各データの点とクラスタセンターとの距離の二乗の総和とクラスタ数の関係を調べて適当なクラス多数を探す必要があります。

実行例

Scikit-learnの公式ページから見ていきましょう。
まずk-meansはどの様な時に使うのかをチートシートで見てみると
  • カテゴリを予想する
  • 正解データがない
  • クラスタ数がわかっている
  • サンプル数 > 10,000
ということでした。

sklearn.cluster.KMeans“詳細ページのExamplesのままやってみましょう。
from sklearn.cluster import KMeans
import numpy as np

# データ作成
X = np.array([[1, 2], [1, 4], [1, 0],
              [10, 2], [10, 4], [10, 0]])

# kmeansをクラスター数2で実行
kmeans = KMeans(n_clusters=2, random_state=0).fit(X)
よく使うのは
  • labels_ : 各点はどのクラスタに判定されるか?
  • kmeans.predict : この点はどのクラスタになるのか
  • cluster_centers_ : クラスタセンターの座標
だと思います。

先に今回のデータの位置関係も見ておきましょう。2次元なので散布図でいいですね。
# 描画用にmatplotlibをインポート
import matplotlib.pyplot as plt

plt.scatter(X[:,0], X[:,1])
ではいろいろ調べてみましょう。
# 各点のクラスタラベル
kmeans.labels_
array([1, 1, 1, 0, 0, 0], dtype=int32)
左側が1で右側が0になりました。
# どのクラスタに割り振られるかを予想
kmeans.predict([[0, 0], [12, 3]])
array([1, 0], dtype=int32)
1個目がクラスタ1、2個目の点がクラスタ0でしたね。右左で考えるとしっくりきますね。
# クラスタセンター
kmeans.cluster_centers_
array([[10., 2.], [ 1., 2.]])
まぁそうですよね。
# 各点のクラスタセンターまでの距離の二乗和
kmeans.inertia_
16.0

2×2が4つで16ですね。

エルボー法

k-means法では計算時にクラスタ数を指定する必要があります。クラスタの核を何個いれるかということですよね。

しかし、クラスタ数はわからないので適当なところを探りましょう。
ここで適当をどう考えるかというとクラスタのセンターがちゃんと重心にある、すなわち各点とクラスタセンターとの距離の総和が小さいのがいいですよね。
クラスタの数が増えると距離の総和は小さくなるに決まっていますが、クラスタ数が多いとまとめる意味がありません
なのでクラス多数を増やした時に距離の総和があまり変わらなくなったらそこが妥当なクラスタ数という風に考えます。

エルボー法をサクッと描写する関数はないので簡単に作ってみました。
X軸がクラスタの数でY軸が各点とクラスタセンターとの距離の二乗総和になります。
import pandas as pd

num_cluster = range(1,7) # x
sum_of_squares = [] # yの箱
change =[] # クラスタが1つの時のsum of squaresを100としたときの比率の箱

# クラスタ数が1の時のSum of squaresを計算しておく
km1 = KMeans(n_clusters=1,random_state=0).fit(X)
first = km1.inertia_

# for文で想定クラスタ数分計算して箱に入れていく
for i  in num_cluster:
    km = KMeans(n_clusters=i, random_state=0).fit(X)
    sum_of_squares.append(km.inertia_)
    change.append(km.inertia_/first*100)

# Dataframeに結果を格納
df = pd.DataFrame({"sum_of_squares":sum_of_squares,
                   "change":change}, index=num_cluster)
print(df.round().astype(int))

#描画
plt.plot(num_cluster,sum_of_squares,marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Sum of Squares')
plt.show()
図を見てみるとクラスタ数が1→2は距離総和が著しく減っているのに対して、2→3以降は大して変化ないですね。大してをどれぐらいにするかは個々の判断で例えばクラスタ1の時の距離総和の “5%以内の変化” 等決めてしまってもいいかと思います。

一方で人って3つとか4つくらいの分け方がちょうどいい部分もあるので問題なさそうならクラスタ数決め打ちでやってみてもいいと思います。

使ってみた感想

教師無し学習なのにクラスタ数がわかっているときに使う方法という矛盾したところはありますが、後追いで確認できれば問題ないと思いました。

今回は2次元データで非常にわかりやすいですが、次元がもっと多い場合は想像するの難しいのでk-meansのようなわかりやすいアルゴリズムを使うといいですね。