scikit-learnとpyclusteringのk-means実装例を比較

# はじめに

Pythonで機械学習を実装する際のデファクトスタンダードはscikit-learnだが、クラスタリングにおいては一部かゆいところに手が届いていない部分もあるため、pyclusteringが選択肢として入ってくる。

しかし、pyclusteringはscikit-learnと比較すると若干使いにくい部分があるため、利用方法の備忘のため、最も基本的なk-meansにおける実装例をまとめる。

# K-meansの実行例
## 使用データ

```python:データ定義
from sklearn.datasets import make_blobs

X, _ = make_blobs(n_features=2, centers=5, random_state=1)
```

```python:散布図
import matplotlib.pyplot as plt

plt.scatter(X[:, 0], X[:, 1])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221698/a1f268de-f96c-5447-c402-b1c888e0296b.png)

## scikit-learn

scikit-learnを用いたk-meansの実装は以下の通りである。

なお、scikit-learnでの初期値設定方法は`init`オプションで設定でき、デフォルトでha
k-means++になっている。

```python:scikit-learnでのk-means
from sklearn.cluster import KMeans

sk_km = KMeans(n_clusters=3).fit(X)
plt.scatter(X[:, 0], X[:, 1], c=sk_km.labels_)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221698/abcb05ca-6198-4044-6573-acd71f6bd5d5.png)



## pyclustering

pyclusteringを用いたk-meansの実装は以下の通りである。

scikit-learnとは異なり、初期値設定とその後のクラスタ学習を分けて指定する必要がある。
後、こちらで用意されている可視化用の関数を使うと少し情報がリッチになる。

```python;pyclusteringでのk-means
from pyclustering.cluster import kmeans
from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer

initial_centers = kmeans_plusplus_initializer(X, 3).initialize()  # k-means++で初期値設定
pc_km = kmeans.kmeans(X, initial_centers)  # kmeansクラスの定義
pc_km.process()  # 学習の実行

_ = kmeans.kmeans_visualizer.show_clusters(X, pc_km.get_clusters(), pc_km.get_centers(), initial_centers=initial_centers)  # 可視化
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221698/abc78c6f-fc8b-ada4-ee9f-bc16d63913e8.png)


pyclusteringで得られたクラスタは、`predict`メソッドと`get_clusters`メソッドで参照できる。

`predict`では、scikit-learnと同様に、入力データに対してラベルを返す。

`get_clusters`は、学習に使ったデータに対するインデックスをクラスタ別に返す。こちらはpandasなどで取り扱うのに適していないので、別途加工する必要がある。(predict使った方が簡単)

```python:predictを用いたクラスタ番号の取得(1)
labels = pc_km.predict(X)
```

```python:get_clustersを用いたクラスタ番号の取得
import numpy as np

clusters = pc_km.get_clusters()
labels = np.zeros((np.concatenate([np.array(x) for x in clusters]).size, ))
for i, label_index in  enumerate(clusters):
    labels[label_index] = i
```

# クラスタ数の決定

## scikit-learn

scikit-learnにおけるエルボー法について示す。

シルエット分析も可能だが、pyclusteringと同様に実装が複雑になるため、割愛する。

なお、どちらにおいても自動的にクラスタ数を決定することはできず、分析者が確認した上で決定する必要がある。

```python:エルボー法
sse = list()
for i in range(1, 11):
    km = KMeans(n_clusters=i).fit(X)
    sse.append(km.inertia_)

plt.plot(range(1, 11), sse, 'o-')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221698/794f843c-267d-c119-5769-0a9fa45be454.png)


## pyclustering

pyclusteringの場合、エルボー法でクラスタ数の決定まで行ってくれる。
なお、クラスタ数は、探索範囲内でクラスタ内誤差平方和が大きく下がったクラスタ数を採用しているようだ。

```python:エルボー法
from pyclustering.cluster.elbow import elbow

kmin, kmax = 1, 10  # 探索する値域
elb = elbow(X, kmin=kmin, kmax=kmax)  # 探索範囲は、kmin~kmax-1までなことに注意
elb.process()
elb.get_amount()  # クラスタ数を参照できる

plt.plot(range(kmin, kmax), elb.get_wce())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221698/5c30347d-000f-fce9-85ec-cfc0b6fd0737.png)



pyclusteringでは、x-meansやg-meansに対応しているため、それを利用することもできる。

```python:x-means
from pyclustering.cluster import xmeans
from pyclustering.cluster.kmeans import kmeans_visualizer

initial_centers = xmeans.kmeans_plusplus_initializer(X, 2).initialize() # k=2以上で探索
xm = xmeans.xmeans(X, initial_centers=initial_centers, )
xm.process()

_ = kmeans_visualizer.show_clusters(X, xm.get_clusters(), xm.get_centers())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221698/3b958d7b-ef05-d0e0-2472-4205e474a72e.png)


```python:g-means
from pyclustering.cluster import gmeans
from pyclustering.cluster.kmeans import kmeans_visualizer

initial_centers = gmeans.kmeans_plusplus_initializer(X, 2).initialize()
gm = gmeans.gmeans(X, initial_centers=initial_centers, )
gm.process()

_ = kmeans_visualizer.show_clusters(X, gm.get_clusters(), gm.get_centers())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221698/f351e223-f9fb-75a6-247d-48ebc5a4da3c.png)


# scikit-learnに比べ、pyclusteringが優位な点

敷居の低さで選ぶのであればscikit-learnではあるが、細かくクラスタリングアルゴリズムに調整したい場合はpyclusteringが優位である。

pyclusteringはそもそも対応しているアルゴリズムが多く、細かく処理内容を定義できる。例えば、距離定義をユークリッド距離からマンハッタン距離やユーザが定義した距離指標に変えることが出来る。

以下にコサイン距離でクラスタリンを行う例を示す。

※コサイン距離でやるだけなら、[spherecluster](https://qiita.com/calderarie/items/5f8b5d0a7b624eeeeb78)の方が簡単

```python:コサイン距離でk-means
import numpy as np
from pyclustering.cluster import kmeans
from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer
from pyclustering.utils.metric import distance_metric, type_metric

X = np.random.normal(size=(100, 2))

def cosine_distance(x1, x2):
    if len(x1.shape) == 1:
        return 1 - np.dot(x1, x2) / (np.linalg.norm(x1) * np.linalg.norm(x2))
    else:
        return 1 - np.sum(np.multiply(x1, x2), axis=1) / (np.linalg.norm(x1, axis=1) * np.linalg.norm(x2, axis=1))

initial_centers = kmeans_plusplus_initializer(X, 8).initialize()
pc_km = kmeans.kmeans(X, initial_centers, metric=distance_metric(type_metric.USER_DEFINED, func=cosine_distance))
pc_km.process()

plt.scatter(X[:, 0], X[:, 1], c=pc_km.predict(X))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221698/84cfbbbc-72dd-59aa-9781-705f8cb2183f.png)



# 参考

- [pyclusteringのxmeansの一部をsklearn風にラッパーする](https://qiita.com/Gold_Gliders/items/729bedddd5f4eee202e8)
- [k-meansの最適なクラスター数を調べる方法](https://qiita.com/deaikei/items/11a10fde5bb47a2cf2c2)
- [Selecting the number of clusters with silhouette analysis on KMeans clustering — scikit-learn 0.22.2 documentation](https://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_silhouette_analysis.html)
- [pyclustering.cluster.elbow.elbow Class Reference](https://pyclustering.github.io/docs/0.8.2/html/d3/d70/classpyclustering_1_1cluster_1_1elbow_1_1elbow.html)
- [自動でクラスタ数を決めてくれるクラスタリング G-means](https://qiita.com/nagomiso/items/fae8a63e06d7c03c7ded)