【機械学習_Python】k-means法

k-means法(k-平均法)についてメモ。

【目次】


前回取り上げた「k-近傍法」とは異なるので注意。
【機械学習_Python】k-近傍法(k-NN) - こちにぃるの日記

k-means法


データのクラスタリングに用いられる手法である。
分類問題における「教師なし学習」に属する。

アルゴリズムは比較的単純で、次の手順となる。

  1. 特徴空間上にk個のランダムな代表点(セントロイド)をプロットする。クラスタ数のkはハイパーパラメータであり、あらかじめ指定する。
  2. 各データとセントロイドの距離を求め、各データを最も近いセントロイドのグループに分類する。
  3. 上記2のグループでの重心(平均)を求め、新たなセントロイドとする。
  4. 上記2、3を繰り返す。
  5. セントロイドが更新されなくなれば終了し、分類を確定する。


f:id:cochineal19:20210524003450p:plain

数式としては、以下の関数を最小化する問題である。
「各クラスタ  C_{k} に属するデータ  x_{i} とセントロイド  \overline{x_{k}} との距離(ユーグリッド距離)の総和」(クラスタ内誤差平方和)を最小化できるセントロイドを求める。

クラスタ内誤差平方和(Sum of Squared errors of prediction、SSE)

 \quad J=\sum ^{K}_{k=1}\sum _{i\in C_{k}}\left\| x_{i}-\overline{x_{k}}\right\| ^{2}

 \qquad K:\verb|クラスタ数|,\ C_{k}:\verb|k番目のクラスタ|

 \qquad x_{i}:\verb|i番目のデータ|,\ \overline{x_{k}}:\verb|k番目のクラスタのセントロイド|


k-means++法


k-means法では、初期値のセントロイドをランダムな位置にプロットするため、複数のセントロイドが近くに位置する可能性があり、分類がうまくいかないことがある。

この問題の改良としてk-means++法があり、セントロイド同士が離れた位置にプロットされやすくなるアルゴリズムである。

エルボー法によるクラスタ数の推定


k-means法でのクラスタ数はハイパーパラメータであり、あらかじめ指定する必要がある。 このクラスタ数の推定方法として「エルボー法」がある。

エルボー法は、クラスタ数ごとのクラスタ内誤差平方和(SSE)を横に並べ、プロットがゆるやかになる位置を最適なクラスタ数とする方法である。

f:id:cochineal19:20210524021551p:plain:w450

Pythonコード


sklearn の cluster の KMeans で実装できる。
パラメタ説明はコメントアウトに記載する。

#--  k-means法
km1 = KMeans(n_clusters=4      # クラスター数指定。
             ,init="k-means++" # 'k-means++' or 'random'(k-means) 。default:'k-means++'
             ,n_init=10        # k-meansの実行回数
             ,random_state=0   # 乱数シード
             )
Y_km = km.fit_predict(X)  # 各データのクラスタ番号を求める
 
#-- SSE値を出力
print("Distortion: %.2f" % km.inertia_)


Irisデータでエルボー法のプロットを作ってみる。
クラスタ数(n_clusters=i)を1~10までループして、各クラスタ数のSSE(km.inertia_ で取得可)をプロットする。

from sklearn import datasets, cluster
import numpy as np
import matplotlib.pyplot as plt
 
#-- データ取得
X = datasets.load_iris()
 
#-- エルボー法(クラスタ数10まで)
SSE = []
for i in range(1, 11):
    km = cluster.KMeans(n_clusters = i
                ,init="k-means++"
                ,n_init=10
                ,random_state=1)
    km.fit(X.data)
    SSE.append(km.inertia_)
 
# グラフのプロット
plt.plot(range(1, 11), SSE, marker="o")
plt.xticks(np.arange(1, 11, 1))
plt.xlabel("クラスタ数")
plt.ylabel("クラスタ内誤差平方和(SSE)")
plt.show()

f:id:cochineal19:20210524025442p:plain:w300

参考


sklearn.cluster.KMeans — scikit-learn 0.24.2 documentation https://www.mext.go.jp/content/20200609-mxt_jogai01-000007843_007.pdf
https://www.jstage.jst.go.jp/article/fss/27/0/27_0_55/_pdf

本ブログは個人メモです。 本ブログの内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。