k-meansを使う機会があったので自分なりにまとめてみました。
なんとなーくわかった気になっていたけどいざ使うとなると、気になることがたくさん出てきたので一つずつ調べながらやってみました。
参考
本当にありがとうございます。
概要
k-means はデータをランダムにクラスターに分類した後、それぞれのクラスターの重心を元に分類を調整し直していくアルゴリズムで、任意のクラスター数(k)に分類可能です。
k-meansのアルゴリズム
- 各点xに対してランダムにクラスターを割り振る。
- 各クラスターの重心(クラスターに含まれる点の平均)を計算する。
- 各点について上記で計算された重心からの距離を計算し、距離が一番近いクラスターに割り当て直す。
- 2と3を、割り当てられるクラスターが変化しなくなるまで行う。
といった感じです。
実践してみる
とりあえず手軽に使えそうなIrisを使ってみました。
データをぶち込む
from sklearn import datasets
from sklearn.cluster import KMeans
iris = datasets.load_iris()
data_in = iris["data"]
clusters=3
model_kmeans = KMeans(n_clusters=clusters)
model_kmeans.fit(data)
こんな感じでn_clustersでクラスター数を指定してk-meansインスタンスを作成し、fitする時にクラスタリングしたいデータを入れてあげます。
クラスタリング結果
クラスタリングの結果はmodel_kmeans.labels_
で見ることができます。n_clustersで3を指定しているので、ここには0, 1, 2のいずれかの番号(クラスター番号)が入っています。
Irisデータでは特徴量ががくの長さ、がくの幅、花びらの長さ、花びらの幅となっています。
まずはがくの長さとがくの幅を軸にとって各点がどのようにクラスタリングされたかみてみましょう。
# ラベル 0 の描画
ldata = data_in[labels == 0]
plt.scatter(ldata[:, 0], ldata[:, 1], color='green')
# ラベル 1 の描画
ldata = data_in[labels == 1]
plt.scatter(ldata[:, 0], ldata[:, 1], color='red')
# ラベル 2 の描画
ldata = data_in[labels == 2]
plt.scatter(ldata[:, 0], ldata[:, 1], color='blue')
# x軸、y軸の設定
plt.xlabel(iris['feature_names'][0])
plt.ylabel(iris['feature_names'][1])
plt.show()
おおー、なんとなく分かれているのがわかりました。
花びらの長さ、花びらの幅でもプロットしてみます。
# ラベル 0 の描画
ldata = data_in[labels == 0]
plt.scatter(ldata[:, 2], ldata[:, 3], color='green')
# ラベル 1 の描画
ldata = data_in[labels == 1]
plt.scatter(ldata[:, 2], ldata[:, 3], color='red')
# ラベル 2 の描画
ldata = data_in[labels == 2]
plt.scatter(ldata[:, 2], ldata[:, 3], color='blue')
# x軸、y軸の設定
plt.xlabel(iris['feature_names'][2])
plt.ylabel(iris['feature_names'][3])
plt.show()
へえー。こっちの方が分かれています。名前にもありますがVerisicolorとVersinicaは近くて、Setosaだけ離れてますね。
t-SNEを使う
このように四つの特徴量があると二個ずつしかプロットできず心苦しいですね。
そこで高次元のデータを可視化するのに使えるという、t-SNEを使ってみました。読み方は「ティースニー」だとか。ティーエスエヌイーが最高に読みづらいと思っていたのでよかった。
原理については先人のqiitaがありますのでこちらを参考にさせていただきました。
https://qiita.com/g-k/items/120f1cf85ff2ceae4aba
from sklearn.manifold import TSNE
# t-SNEをやる
tsne = TSNE(n_components=2)
vecs = tsne.fit_transform(data_in)
# ラベル 0 の描画
ldata = vecs[labels == 0]
plt.scatter(ldata[:, 0], ldata[:, 1], color='green', label='Setosa')
# ラベル 1 の描画
ldata = vecs[labels == 1]
plt.scatter(ldata[:, 0], ldata[:, 1], color='red', label='Versicolor')
# ラベル 2 の描画
ldata = vecs[labels == 2]
plt.scatter(ldata[:, 0], ldata[:, 1], color='blue', label='Versinica')
plt.legend()
plt.show()
おおおVerisicolorとVersinicaがかなり近いですが綺麗に分かれてる気がする。
Irisじゃないときどう使うの?
とりあえずここまでいい感じにk-meansでクラスタリングができました。
けどクラスター数がわからない時どうすればいいんだという壁にぶち当たりました。
しかしどんな時も先達はいてくれるものですね。感謝。
https://qiita.com/deaikei/items/11a10fde5bb47a2cf2c2
どうやら「エルボー法」と「シルエット分析」というのがクラスター数を決めるときの参考になるらしい。
クラスター数がわからないデータの例が思い浮かばないのでとりあえずIrisデータセットを入れてみます。「3が最適」みたいな結果になればまあ嬉しいかな。
ということでほとんど先人のコードで恐縮ですが、まずはエルボー法から
distortions = []
for i in range(1,11): # 1~10クラスタまで一気に計算
model_kmeans = KMeans(n_clusters=i,
n_init=10,
max_iter=300,
random_state=0)
model_kmeans.fit(data_in) # クラスタリングの計算を実行
distortions.append(model_kmeans.inertia_) # km.fitするとkm.inertia_が得られる
plt.plot(range(1,11),distortions,marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.show()
なるほど確かに3のとこでサチっている。2の点でもかなり角度がついているが、これはVerisicolorとVersinicaが近いからだと考えるとなるほーっていう感じであります。
続いてシルエット分析をしてみます。もうこれに関しても先ほどの人のコードのまんまで、どこまで引用していいのかわからないので割愛。本当にありがとうございます。
シルエットの高さが各クラスターのサイズを示しているらしく、これが比較的均等になればいい感じのクラスター数だねという評価になるらしいです。なるほど。まあいいのかな。
けど赤点線を超えているデータは1, 3のクラスター数においては少ないです。
シルエット係数(silhouette coefficient)が1に近いほど他のクラスターと離れているらしいのですが、どうやらこの2というやつがSetosaぽいですね。1と2はクラスター係数が比較的小さいので近接しているクラスターということになります。クラスターのサイズ(幅)的に1がVerisicolor、3がVersinicaに対応するのかな
終わりに
いろんなとこで最初の方に出てくるk-meansでしたが実際使うとなるといろんな疑問が生じてきました。せっかくだからメモしよと思って書いてたら、なんとなく投稿したくなってしまいました。
あとk-meansのデメリットとして最初のランダムのクラスタリングによって、うまくクラスタリングできなくなる可能性があるというのがあるらしく、それを克服するためにKMeans++というものもあるらしいです。
それも書こうと思ったけど、力尽きてしまいました。
今後もゆるゆるアウトプットしたいものです。