
こんにちは。AI研究所のショウです。
今回の記事の概要は、
scikit-learnというPython機械学習ライブラリを使って、
クラスタリング(クラスター分析)を試してみます。
クラスタリングとは?
クラスタリングとは、大きな集団の中から
似たもの同士を集めて、グループに分けることをいいます。
しかし、性別や年齢など、
わかりやすい判断基準の集団に分けるのは、
クラスタリングとは呼べません。
クラスタリングは、1つの大きなデータを
近しいデータ毎に各クラスター(房(ふさ)や、群れ、集団という意味)
に分けたのち、そのクラスターにどんな意味があるのかを、
人間が意味づけをしていきます。
なにはともあれ、さっそく
Scikit-learnを使ってプログラムを実装していきましょう。
クラスタリングを試してみよう – データを作る
以下、店舗などのサイトがあるとして、
架空のデータを作ります。
まず、今回の分析したい目標をふんわり決めます。
「店舗のサイトに訪れる客層を知りたい」にしましょう。
目標が決まったので、次にデータを作成します。
今回のデータ収集方法は、簡単なアンケートです。
合計として、約1000人のデータを集めたとします。
また、解答欄には「フリー記入欄」なく、全て選択式です。
アンケート項目は以下です。
Q1:一日にどれぐらいネットをご利用になりますか?
1:一日5時間以上
2:一日3時間以上
3:一日1時間以上
4:一日1時間未満
Q2:普段、雑誌などは読まれますか?
1:一年に一冊以上
2:一ヶ月に一冊以上
3:一週間に一冊以上
4:一日一冊以上
5:まったく読まない
Q3:一日にテレビはどれくらい見られますか?
1:一日五時間以上
2:一日三時間以上
3:一日一時間以上
4:一日一時間未満
5:まったく見ない
Q4:普段よく使う移動手段はなんですか?
1:電車
2:バス
3:徒歩
4:自転車
5:オートバイ
6:自動車
Q5:普段からよく使っているものはいつ買ったものですか?
1:ここ1週間
2:一ヶ月以内
3:半年以内
4:一年以内
5:三年未満
6:三年以上前
Q6:常に最新のトレンドを知っていないと落ち着かないですか?
1:まったくそう思わない
2:少し思う
3:普通
4:すごく思う
Q7:趣味はありますか?
1:趣味はない
2:ゲーム、ネット、コンピュータ系
3:読書
4:動画、映像を見る
5:音楽
6:アウトドア全般
7:ファッション全般
8:旅行、レジャー全般
9:スポーツ全般
Q8:どのような家族構成ですか。
1:答えたくない
2:兄がいる
3:姉がいる
4:弟がいる
5:妹がいる
6:一人っ子
Q9:外によく買い物にいくほうですか
1:一日一回以上はいく
2:週に2〜3回程度
3:一ヶ月に4〜5回程度
4:ほとんどネットショッピングで済ませる
5:一年に数回
Q10:興味があることに費やす時間はどれくらいですか。
1:一日5時間以上
2:一日3時間以上
3:一日1時間以上
4:一日1時間未満
5:まったくしてない
内容は適当です。
思いついたことを約10問にまとめました。
次にクラスタリングしやすいようにアンケートの回答を数値化します。
単純に回答の数字とします。
それでは、プログラミングしていきましょう。
import pandas as pd import numpy as np col=['ネット時間','雑誌時間','テレビ時間','移動手段','購入頻度','トレンド','趣味','家族構成','買い物','興味'] row=[] for _ in range(1000): web=np.random.randint(1,5) magazin=np.random.randint(1,6) tv=np.random.randint(1,6) trans=np.random.randint(1,7) tool=np.random.randint(1,7) trend=np.random.randint(1,5) hobby=np.random.randint(1,10) family=np.random.randint(1,7) outside=np.random.randint(1,6) intrests=np.random.randint(1,6) row.append(np.array([web,magazin,tv,trans,tool,trend,hobby,family,outside,intrests])) features=pd.DataFrame(row,columns=col)
アンケートの質問を簡単にして列名に。
回答をnumpyのランダムを使って行にします。
1000回、行を作って変数rowに格納していきます。
最後にpandasのデータフレームを作成します。
確認してみましょう。
回答はランダムな数字になっているようです。
クラスタリングを試す
続いて本題のScikit-learnによるクラスタリングです。
今回はクラスタリングにおいて、
よく使われているK-means法を使います。
以下をインポートしてください。
from sklearn.cluster import KMeans
Kmeans法を使うのはとても簡単です。
pred=KMeans(n_clusters=4).fit_predict(features)
これでクラスター分けができました。
確認してみましょう。
うまく分けられているようです。
詳細を確認します
詳細を確認してみましょう。
features['cluster_id']=pred features['cluster_id'].value_counts()
クラスターIDを学習用データフレームにくっつけます。
そして、value_counts()メソッドで数を確認します。
だいたい、同じくらいの数に分けられています。
各クラスターごとの平均値などを見ていきましょう。
id1=features[features['cluster_id']==0] id2=features[features['cluster_id']==1] id3=features[features['cluster_id']==2] id4=features[features['cluster_id']==3] ids=[id1,id2,id3,id4] [display(_.mean()) for _ in ids]
クラスターの数はどうやって決める?
さて、ここで疑問です。
本当にこのクラスター数でいいのか。
結果がいまいちのとき、どうしたら
次に考える最適なクラスター数を求めるのか。
それを調べる方法として、エルボー法とシルエット法があります。
まず、エルボー法で調べてみましょう。
エルボー法を試してみる
https://qiita.com/deaikei/items/11a10fde5bb47a2cf2c2
こちらのサイトを参考にしてコードを作成してみました。
import matplotlib.pyplot as plt %matplotlib inline distortions = [] for i in range(1,11): # 1~10クラスタまで一気に計算 km = KMeans(n_clusters=i, init='k-means++', # k-means++法によりクラスタ中心を選択 n_init=10, max_iter=300, random_state=0) km.fit(features) # クラスタリングの計算を実行 distortions.append(km.inertia_) # km.fitするとkm.inertia_が得られる plt.plot(range(1,11),distortions,marker='o') plt.xlabel('Number of clusters') plt.ylabel('Distortion') plt.show()
ガクンと角度が変わるところに注目です。
この肘のような形になることからエルボー法と呼ばれてます。
さて、どうやら矢印の場所らへんが怪しそうです。
4は試したので、8にしてみましょう。
あまりクラスターを分けすぎると判断がしづらくなります。
pred=KMeans(n_clusters=8).fit_predict(features) features['cluster_id']=pred features['cluster_id'].value_counts()
シルエット法を試してみる
続いてシルエット法です。
かんたんに説明すると
下のような図になっていて、
クラスター=8
すべてのラインが赤い線を超えていて、
すべてのラインの太さが均一であれば最適であろうとされています。
from sklearn.metrics import silhouette_samples from matplotlib import cm km = KMeans(n_clusters=8, # クラスターの個数 init='k-means++', # セントロイドの初期値をランダムに設定 n_init=10, # 異なるセントロイドの初期値を用いたk-meansあるゴリmズムの実行回数 max_iter=300, # k-meansアルゴリズムの内部の最大イテレーション回数 tol=1e-04, # 収束と判定するための相対的な許容誤差 random_state=0) # セントロイドの初期化に用いる乱数発生器の状態 y_km = km.fit_predict(features) cluster_labels = np.unique(y_km) # y_kmの要素の中で重複を無くす n_clusters=cluster_labels.shape[0] # 配列の長さを返す。つまりここでは n_clustersで指定した3となる # シルエット係数を計算 silhouette_vals = silhouette_samples(features,y_km,metric='euclidean') # サンプルデータ, クラスター番号、ユークリッド距離でシルエット係数計算 y_ax_lower, y_ax_upper= 0,0 yticks = [] for i,c in enumerate(cluster_labels): c_silhouette_vals = silhouette_vals[y_km==c] # cluster_labelsには 0,1,2が入っている(enumerateなのでiにも0,1,2が入ってる(たまたま)) 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.yticks(yticks,cluster_labels + 1) # クラスタレベルを表示 plt.ylabel('Cluster') plt.xlabel('silhouette coefficient') plt.show()
4個、
12個
今回は8個にします。
id1=features[features['cluster_id']==0] id2=features[features['cluster_id']==1] id3=features[features['cluster_id']==2] id4=features[features['cluster_id']==3] id5=features[features['cluster_id']==4] id6=features[features['cluster_id']==5] id7=features[features['cluster_id']==6] id8=features[features['cluster_id']==7] ids=[id1,id2,id3,id4,id5,id6,id7,id8] [display(_.mean()) for _ in ids]
趣味の平均値が大きく離れています。
数字が小さいほどインドア派、
大きいほどアウトドア派とざっくり分けます。
[print(f"id_{i} -> {_['趣味'].mean()}") for i,_ in enumerate(ids)]
クラスターIDの、
0、2、5、7と
1、3、4、6を分けます。
このように分けていき、
あとは細かい分析をしていきます。
まとめ
以上ですが、Scikit-learnを使えば、たった2〜3行でクラスタリングができてしまいます。
皆様も、会社のデータなどを入れて試してみてください!
K-meansクラスタリングも学べるAI・機械学習セミナー(リンク)も開催中!ノープログラミングでも学べます。