【22日目】クラスタリングをやってみる【2021アドベントカレンダー】
2021年1人アドベントカレンダー(機械学習)、22日目の記事になります。
テーマは クラスタリング になります。
やや 回帰による予測 からは脱線して、データセットからできることを試してみます。
Colab のコードはこちら
クラスタリングは、クラスタに分けるべきなのか、目安がありません。
完璧とはいかないものの、適切なクラスター数の参考情報を得られる「エルボー法」と「シルエット分析」を試してみる。
エルボー法
エルボー法とは、クラスタごとのSSE(クラスタ内誤差平方和)値をプロットした図で、SSE値が"ヒジ"のように曲がった点が最適なクラスター数とみなす手法。
クラスター内平方和は、各観測値やクラスター重心から得られる偏差平方和で、各クラスター内の観測値のばらつきの測度を表す。
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つのクラスターが隣接するクラスターとどれくらい離れているかを可視化する手法である。
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})
TSNE による主成分分析と合わせてクラスタリング結果を可視化してみましょう。
TSNE のパラメーターの1つである perplexity を少しずつ変えながら、分布の変化を見てみます。
perplexity とは、どれだけ近傍の点を考慮するかを決めるための値、のようです。
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日目は以上になります、最後までお読みいただきありがとうございました。
参考サイト
手前味噌 1
手前味噌 2
Discussion