🌃

【22日目】クラスタリングをやってみる【2021アドベントカレンダー】

2021/12/22に公開

2021年1人アドベントカレンダー(機械学習)、22日目の記事になります。

https://qiita.com/advent-calendar/2021/solo_advent_calendar

テーマは クラスタリング になります。

https://qiita.com/kotap15/items/38289edfe822005e1e44

やや 回帰による予測 からは脱線して、データセットからできることを試してみます。

Colab のコードはこちら Open In Colab

クラスタリングは、クラスタに分けるべきなのか、目安がありません。

完璧とはいかないものの、適切なクラスター数の参考情報を得られる「エルボー法」と「シルエット分析」を試してみる。

エルボー法

エルボー法とは、クラスタごとのSSE(クラスタ内誤差平方和)値をプロットした図で、SSE値が"ヒジ"のように曲がった点が最適なクラスター数とみなす手法。

https://qiita.com/deaikei/items/11a10fde5bb47a2cf2c2

クラスター内平方和は、各観測値やクラスター重心から得られる偏差平方和で、各クラスター内の観測値のばらつきの測度を表す。

https://support.minitab.com/ja-jp/minitab/18/help-and-how-to/modeling-statistics/multivariate/how-to/cluster-k-means/interpret-the-results/all-statistics-and-graphs/

distortions = []
num = 20

for i  in range(1,num+1):
    model = KMeans(n_clusters=i,
                init='k-means++',
                n_init=10,
                max_iter=300,
                random_state=SEED
    )
    
    model.fit(df_tf)

    distortions.append(model.inertia_)

plt.title("エルボー法")
plt.plot(
    range(1,num+1),
    distortions,
    marker='o'
    )

plt.xlabel('クラスター数')
plt.xticks( np.arange(0, num, 2))
plt.ylabel('歪み')
plt.show()

シルエット分析

シルエット分析は、クラスタ内のサンプルがどの程度密にグループされているか(凝集度)の目安となるグラフを可視化し、1つのクラスターが隣接するクラスターとどれくらい離れているかを可視化する手法である。

https://qiita.com/deaikei/items/11a10fde5bb47a2cf2c2

num = 8
fig = plt.figure(figsize=(10,20))

for k in range(2, num+1):
    
    kmeans_num = KMeans(
        n_clusters=k, 
        random_state=SEED
        ).fit_predict(df_tf)

    cluster_labels = np.unique(kmeans_num)       # y_kmの要素の中で重複を無くす
    n_clusters=cluster_labels.shape[0]           # 配列の長さを返す。つまりここでは n_clustersで指定した数となる

    # シルエット係数を計算
    silhouette_vals = silhouette_samples(
                                            df_tf,
                                            kmeans_num,
                                            metric='euclidean'
                                        )  # サンプルデータ, クラスター番号、ユークリッド距離でシルエット係数計算

    y_ax_lower, y_ax_upper= 0,0
    yticks = []

    # グラフ
    plt.subplot((num//3+1), 3, k-1)
    
    for i,c in enumerate(cluster_labels):
        c_silhouette_vals = silhouette_vals[kmeans_num==c]
        c_silhouette_vals.sort()
        y_ax_upper += len(c_silhouette_vals)              # サンプルの個数をクラスターごとに足し上げてy軸の最大値を決定
        color = cm.jet(float(i)/n_clusters)               # 色の値を作る
        plt.barh(range(y_ax_lower,y_ax_upper),            # 水平の棒グラフのを描画(底辺の範囲を指定)
                         c_silhouette_vals,               # 棒の幅(1サンプルを表す)
                         height=1.0,                      # 棒の高さ
                         edgecolor='none',                # 棒の端の色
                         color=color)                     # 棒の色
        yticks.append((y_ax_lower+y_ax_upper)/2)          # クラスタラベルの表示位置を追加
        y_ax_lower += len(c_silhouette_vals)              # 底辺の値に棒の幅を追加

    silhouette_avg = np.mean(silhouette_vals)                 # シルエット係数の平均値
    plt.axvline(silhouette_avg,color="red",linestyle="--")    # 係数の平均値に破線を引く
    plt.title(f"クラスター数 {k}")
    plt.yticks(yticks,cluster_labels + 1)                     # クラスタレベルを表示
    plt.ylabel('クラスター数')
    plt.xlabel('シルエット係数')

plt.tight_layout()

なんとなくですが、5個くらいのクラスタに分けるのが良いのではないでしょうか。

k-means法によるクラスタリング

パイプラインに KMeans ライブラリを組み込みます。

columns_transformers 以前の内容は割愛していますが詳細は Colab参照。

# パイプラインの作成
k = 5

pipe = Pipeline(
    [
        ("columns_transformers", columns_transformers),
        ('model', KMeans(
                    n_clusters=k,
                    random_state=SEED,
                    init='k-means++',
                    n_init=10,
                    max_iter=300,
                )
         )
     ]
)

クラスタリングによるラベルの分布を確認してみましょう。

pipe.fit(df)

labels = pipe.predict(df)

print(collections.Counter(labels))

出力:
Counter({3: 11822, 4: 11748, 0: 11522, 1: 10934, 2: 9766})

https://note.nkmk.me/python-collections-counter/

TSNE による主成分分析と合わせてクラスタリング結果を可視化してみましょう。

TSNE のパラメーターの1つである perplexity を少しずつ変えながら、分布の変化を見てみます。

perplexity とは、どれだけ近傍の点を考慮するかを決めるための値、のようです。

https://bunsekikobako.com/t-sne-abstract/

fig = plt.figure(figsize=(12,6))

num = 6

for i in range(2, num):
    plt.subplot((num//4+1), 4, i-1)
    plt.title(f"perplexity: {i}")
    
    model_tsne = TSNE(n_components=2, perplexity=i)
    feature = model_tsne.fit_transform(df_tf)

    for label in natsorted(np.unique(labels)):

        colors = [plt.get_cmap("tab20")(x) for x in np.array(labels)[labels==label]]

        plt.scatter(feature[:, 0][labels==label], feature[:, 1][labels==label], alpha=0.8, color=colors, label=label)

    plt.legend(bbox_to_anchor=(1, 1), loc='upper right', borderaxespad=0)

plt.tight_layout()

perplexity が 2 から 5 になるにつれ、各クラスタ番号の分布が明瞭になってきたように見えますね。

22日目は以上になります、最後までお読みいただきありがとうございました。

参考サイト

https://qiita.com/deaikei/items/11a10fde5bb47a2cf2c2

手前味噌 1
https://zenn.dev/megane_otoko/articles/030_clustering

手前味噌 2
https://zenn.dev/megane_otoko/articles/2021ad_11_principal_component_analysis

Discussion