混合ガウスモデルによるクラスタリングをJupyter Notebook上で試す

前回の
[SVMをJupyter Notebook上のscikit-learnで試す](https://qiita.com/suzuki-navi/items/5745eff3392ec9f75249)
の続編みたいな記事です。

これ以降は、
[Jupyter NotebookをDockerを使って簡単にインストールし起動（nbextensions、Scalaにも対応） - Qiita](https://qiita.com/suzuki-navi/items/37615024d4748aac6688)
に従って準備したJupyter Notebookの環境で試しています。

このJupyterの環境でブラウザで8888番ポートにアクセスして、Jupyter Notebookを使うことができます。右上のボタンのNew > Python3をたどると新しいノートを開けます。

適当にランダムで作成したCSVファイル
https://github.com/suzuki-navi/sample-data/blob/master/sample-data-2.csv
も使っています。

# データ確認

sample-data-2.csv で試してみます。

```
import pandas as pd
from sklearn import model_selection
df = pd.read_csv("sample-data-2.csv", names=["id", "target", "data1", "data2"])
```

こんなデータです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/67e65dc3-6957-060b-b4ce-a1073d33b160.png)

```
%matplotlib inline
import matplotlib.pyplot as plt
plt.scatter(df["data1"], df["data2"])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/3b9b7717-60e5-41f7-4efd-a6dd5776729a.png)

これだけだと伝わらないのですが、4つのクラスタがあります。4つがわかるようにこのあと散布図をだらだら並べますが、先に要約すると、次の4つです。

A. まわりに散らばってる少量のデータ
B. 中央付近の大きなデータの塊
C. 中央付近のさらに中心部に小さくまとまっているデータの塊
D. 中央付近のさらに中心部のやや右上にごく小さく固まっているデータの塊

以下、色分けした散布図です。

```
plt.scatter(df["data1"], df["data2"], c = df["target"])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/525b19e7-2574-5536-950b-e1d9c46cfbf1.png)

大多数の中央付近のデータ(B緑, C青, D紫)と、少ないですがまわりに散らばっているデータ(A黄)とがあります。

中央付近を拡大してみます。

```
plt.xlim(-5, 5)
plt.ylim(-5, 5)
plt.scatter(df["data1"], df["data2"], c = df["target"])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/ba731bb8-50c8-cc28-9afb-c435fbe71423.png)

緑のエリアの中には2つの濃い塊(C青, D紫)があります。

さらに大きく拡大してみます。

```
plt.xlim(-2, 2)
plt.ylim(-2, 2)
plt.scatter(df["data1"], df["data2"], c = df["target"])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/4ecbcd78-2e94-3fff-d9f8-d8cde0b10985.png)

中心部がC青で右上がD紫です。

このデータを生成した以下のコードを見ていただければわかるのですが、色ごとに正規分布でランダムに分布させたのを4色重ね合わせただけです。

https://github.com/suzuki-navi/sample-data/blob/master/sample-data-2.py

この4色を教師なしでクラスタリングしたいです。

# K-meansを試してみる

混合ガウスモデルの前に、同じく教師なしクラスタリングのK-meansを試してみます。データの分布状況からしてK-meansでは無理そうなのですが、やりかたを把握するためにいちおう。

とりあえず

```
feature = df[["data1", "data2"]]
target = df["target"]
```

K-meansのモデルで学習します。クラスタ数は4つと指定しておきます。

```
from sklearn import cluster
model = cluster.KMeans(n_clusters=4)
model.fit(feature)
```

参考
[sklearn.cluster.KMeans — scikit-learn 0.21.3 documentation](https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html)

`mlxtend`というパッケージに含まれる`plotting.plot_decision_regions`を使うとどのように分類しているのかを散布図で視覚化してくれます。`plot_decision_regions`にはPandasのオブジェクトではなくNumPyの配列を渡す必要がありますので、`to_numpy()`というメソッドで変換します。

```
from mlxtend import plotting
plotting.plot_decision_regions(feature.to_numpy(), target.to_numpy(), clf=model)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/8310ad70-efab-556e-98bb-b721a187a1e9.png)

見てのとおり、やはりまったくだめでした。

参考
[plot_decision_regions - Mlxtend.plotting - mlxtend](http://rasbt.github.io/mlxtend/api_subpackages/mlxtend.plotting/#plot_decision_regions)
[pandas.DataFrame.to_numpy — pandas 0.25.3 documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_numpy.html)


# 混合ガウスモデルを試してみる

本題の混合ガウスモデルを試してみます。

参考
[sklearn.mixture.GaussianMixture — scikit-learn 0.21.3 documentation](https://scikit-learn.org/stable/modules/generated/sklearn.mixture.GaussianMixture.html)

```
from sklearn.mixture import GaussianMixture
model = GaussianMixture(n_components=4, covariance_type='full')
model.fit(feature)
```

```
plotting.plot_decision_regions(feature.to_numpy(), target.to_numpy(), clf=model)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/c19558d9-3474-2b30-baed-2aaa6c3d93e4.png)

うーん、期待していたほどではない・・・

`plot_decision_regions`での拡大の仕方がわからなかったので普通にmatplotlibで分類結果の中央部を見てみます。

```
pred = model.predict(feature)
plt.xlim(-5, 5)
plt.ylim(-5, 5)
plt.scatter(feature["data1"], df["data2"], c = pred)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/6225e31e-69da-eb91-7946-6f357fdc47d5.png)

中心部の2つの塊が1つにまとまってしまいました。

学習にランダムな要素があるので、なんどか試してみたのですが、中心部の2つの塊を分離することはなく、それ以外のところは毎度バラバラな境界線を描くようです。

# 独自(?)ロジック

唐突ですがここで、正規分布に従うクラスタを分離できるように、K-meansをもとに独自にコードを書いてみました。

独自のコードはとりあえずScalaで書いたのですが、詳細はここでは省略します。余裕があったら別の記事で紹介します。

独自のコードでクラスタリングした結果を pred1.csv に保存して、散布図で見てみます。

```
pred1 = pd.read_csv("pred1.csv", names=["pred"])
plt.scatter(feature["data1"], feature["data2"], c = pred1["pred"])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/5c062dbb-2cd7-4ef3-b317-6da18f63aacd.png)

よさそうです。

中央部を拡大します。

```
plt.xlim(-5, 5)
plt.ylim(-5, 5)
plt.scatter(feature["data1"], feature["data2"], c = pred1["pred"])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/6cbf940a-88ca-1242-badf-6ac8965d1631.png)

```
plt.xlim(-2, 2)
plt.ylim(-2, 2)
plt.scatter(feature["data1"], feature["data2"], c = pred1["pred"])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/63b13b33-0b19-8a04-b2a5-ca1b650acd9c.png)

4つにきれいに分離できました。

# 経緯

K-meansではきれいに分離できないこのサンプルデータのケースで教師なしで分離したいってのがこの記事の動機ではあったのですが、記事の順番は違っていまして、混合ガウスモデルが最初よくわからなくて、さきに独自ロジックを書きました。K-meansを改良したものです。

独自ロジックであまりにもきれいに分離できて感動したのですが、きっと既知のアルゴリズムを再発明したにすぎないと思って @stkdev さんに相談したら、混合ガウスモデルでできると指摘を受けたものです。混合ガウスモデルのアルゴリズムがまだ理解しきれていなくて、私が書いた独自ロジックと同じものなのかどうかはよくわかっていないのです。同じかもしれないですが、少なくとも似てそうだとまでは思ってます。

ただscikit-learnで混合ガウスモデルで試してみてもいまいちきれいに出なかったのは、チューニングの問題なのか、これもよくわかってないです。

独自ロジックは余裕があったら別に投稿します。