-Python- クラスタリング k-means

クラスタリングには様々な手法がありますが,以下にk-meansを用いた例を示します.k-meansは1950年代に発表された比較的古い手法のようですが,計算が簡単であり直感的でわかりやすいというメリットがあり,現代でも使用されています.
k-meansはクラスタ数をあらかじめ指定して,以下のような手順で漸進的にクラスタ化を進める手法です.

  1. 各データを何らかの手段でクラスタに割り振ります.クラスタ中心を最初に決めて初期クラスタを形成する場合もあります.初期化手法はランダムでも構わないのですが,後々の計算が効率的になる k-means++ 法という手法がよく用いられます.
  2. クラスタ毎に中心を計算します.一般にはクラスタに属するデータ点の算術平均を用いることが多いようです.
  3. 各データからクラスタ中心への距離を求め,もし,データが最も近いクラスタ以外に属しているようであれば,データの所属を最も近いクラスタに変更します.
  4. 3. の手順でクラスタが変更になっていなければ,もしくは事前に決めた閾値よりも変化量が小さい場合は,処理が終了となります.
  5. 新しいクラスタの割り振りを使って,2. からの処理を繰り返します.

以下に Iris データセットを用いて k-means によってクラスが形成されていく様子を見ることができるプログラム例を示します.

 
# Import modules
from __future__ import unicode_literals
import numpy as np
import matplotlib.pyplot as plt
from sklearn import cluster
from sklearn import datasets
 
# Load iris data set
iris = datasets.load_iris()
data = iris["data"]
 
# Define initial center point
init_centers=np.array([
       [4,2.5,3,0],
       [5,3  ,3,1],
       [6,4  ,3,2]])
 
# Retrieving data definitions and values
x_index = 1
y_index = 2
 
data_x=data[:,x_index]
data_y=data[:,y_index]
 
# definition of scale and labels
x_max = 4.5
x_min = 2
y_max = 7
y_min = 1
x_label = iris["feature_names"][x_index]
y_label = iris["feature_names"][y_index]
 
def show_result(cluster_centers,labels):
    # Draw cluster 0 and center point
    plt.scatter(data_x[labels==0], data_y[labels==0],c='black' ,
                alpha=0.3,s=100, marker="o",label="cluster 0")
    plt.scatter(cluster_centers[0][x_index], cluster_centers[0][y_index],
                facecolors='white', edgecolors='black', s=300, marker="o")
 
     # Draw cluster 1 and center point
    plt.scatter(data_x[labels==1], data_y[labels==1],c='black' ,
                alpha=0.3,s=100, marker="^",label="cluster 1")
    plt.scatter(cluster_centers[1][x_index], cluster_centers[1][y_index],
                facecolors='white', edgecolors='black', s=300, marker="^")
 
     # Draw cluster and center point
    plt.scatter(data_x[labels==2], data_y[labels==2],c='black' ,
                alpha=0.3,s=100, marker="*",label="cluster 2")
    plt.scatter(cluster_centers[2][x_index], cluster_centers[2][y_index],
                facecolors='white', edgecolors='black', s=500, marker="*")
 
    # Set the scale and axis label
    plt.xlim(x_min, x_max)
    plt.ylim(y_min, y_max)
    plt.xlabel(x_label,fontsize='large')
    plt.ylabel(y_label,fontsize='large')
    plt.show()
 
# Draw initial status
labels=np.zeros(len(data),dtype=np.int)
show_result(init_centers,labels)
 
for i in range(5):
model = cluster.KMeans(n_clusters=3,max_iter=1,init=init_centers).fit(data)
labels = model.labels_
init_centers=model.cluster_centers_

 

show_result(init_centers,labels)
 
実行結果は以下のようになります.

f:id:HidehikoMURAO:20181017000847p:plain

f:id:HidehikoMURAO:20181017000859p:plain

f:id:HidehikoMURAO:20181017000909p:plain

 

f:id:HidehikoMURAO:20181017001144p:plain

f:id:HidehikoMURAO:20181017001155p:plain

f:id:HidehikoMURAO:20181017001210p:plain

scikit-learnにおける k-means は,これまでの章で説明してきた分類と同様に,対応する分類器を作った後に学習を行います(fit).irisデータをロードして k-means で3つのクラスタを作る場合に必要なのは,以下のコードになります.

 
from sklearn import cluster
from sklearn import datasets
 
iris = datasets.load_iris()
data = iris["data"]
 
model = cluster.KMeans(n_clusters=3)
        model.fit(data)

上記のコードで4行目はIrisデータをロードしています.scikit-learn においてクラスタリングは sklearn.clusterモジュールにまとまっています.
k-means は,sklearn.cluster.KMeansを指定して,fit を呼び出すと k-means によるクラスタ化が行われます.戻り値の属性にクラスタリング結果の各種情報が含まれています.分類の結果ラベルのみが必要な場合には fit_predict メソッドを呼び出します.
パラメータ n_clusters=クラスタ数を指定しています.n_clustersのデフォルト値は8です(通常指定するのはn_clustersのみです).

クラスタリングの結果は,分類器の属性(すなわちインスタンス変数)に保存されます.関連する項目は,以下の3つになります.

  1. cluster_centers_クラスタ中心の座標
  2. labels:各点に対するラベル
  3. inertia_:各データ点からそれぞれが属するクラスタ中心までの距離の総和

クラスタリングの結果は label_ にあります.

print(model.labels_)

とすると,以下のように出力されます.

[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 2 2 2 1 2 2 2 2
 2 2 1 1 2 2 2 2 1 2 1 2 1 2 2 1 1 2 2 2 2 2 1 2 2 2 2 1 2 2 2 1 2 2 2 1 2
 2 1]

クラスタは,0 - 2でラベル化されており,対応するデータがどのクラスタに属しているかを示しています.

クラスタリングの結果(今回は花弁の長さ -petal length-と,幅 -petal width-)を散布図で可視化しています.グラフの描画に,以下のようなプログラムを作成してみます.

 
# Import modules
import matplotlib.pyplot as plt
from sklearn import cluster
from sklearn import datasets
 
Load iris data set
iris = datasets.load_iris()
data = iris['data']
 
# Learning and generate cluster
model = cluster.KMeans(n_clusters=3)
model.fit(data)
 
# Get labels for learning results
labels = model.labels_
 
# Draw graph
ldata = data[labels == 0]
plt.scatter(ldata[:, 2], ldata[:, 3],
                  c='black' ,alpha=0.3,s=100 ,marker="o")
 
ldata = data[labels == 1]
plt.scatter(ldata[:, 2], ldata[:, 3],
                  c='black' ,alpha=0.3,s=100 ,marker="^")
 
ldata = data[labels == 2]
plt.scatter(ldata[:, 2], ldata[:, 3],
                  c='black' ,alpha=0.3,s=100 ,marker="*")
 
Setting axis labels
plt.xlabel(iris["feature_names"][2],fontsize='large')
plt.ylabel(iris["feature_names"][3],fontsize='large')
 
 
plt.show()
 

実行結果は,以下のようになります.

f:id:HidehikoMURAO:20181017001342p:plain

6-12行目はデータのロードとクラスタリングの実行になります.それ以降は散布図を描画するコードです.18-20, 22-24, 26-28行目ではクラスタ毎にマーカを変える指定をしています.
 
上記の例では,花弁の長さ -petal length-と,幅 -petal width-の組み合わせでしたが,4つの属性のうちの2つずつの組み合わせ(計6種類)について散布図を作るプログラムは以下のようになります.

# Import modules
import matplotlib.pyplot as plt
from sklearn import cluster
from sklearn import datasets
 
 
Load iris data set
iris = datasets.load_iris()
data = iris['data']
 
# Learning and generate cluster
model = cluster.KMeans(n_clusters=3)
model.fit(data)
 
 Get labels for learning results
labels = model.labels_
 
# Draw graph
MARKERS = ["o", "^" , "*" , "v", "+", "x", "d", "p", "s", "1", "2"]
 
function that creates a scatter plot 
#    with the feature value of the specified index
def scatter_by_features(feat_idx1, feat_idx2):
    for lbl in range(labels.max() + 1):
        clustered = data[labels == lbl]
        plt.scatter(clustered[:, feat_idx1], clustered[:, feat_idx2],
                    c='black' ,alpha=0.3,s=100,
                    marker=MARKERS[lbl], label='label {}'.format(lbl))
 
    plt.xlabel(iris["feature_names"][feat_idx1],fontsize='xx-large')
    plt.ylabel(iris["feature_names"][feat_idx2],fontsize='xx-large')
 
plt.figure(figsize=(16, 16))
 
# feature "sepal length" and "sepal width"
plt.subplot(3, 2, 1)
scatter_by_features(0, 1)
 
# feature "sepal length" and "petal length"
plt.subplot(3, 2, 2)
scatter_by_features(0, 2)
 
# feature "sepal length" and "petal width"
plt.subplot(3, 2, 3)
scatter_by_features(0, 3)
 
# feature "sepal width" and "petal length"
plt.subplot(3, 2, 4)
scatter_by_features(1, 2)
 
# feature "sepal width" and "petal width"
plt.subplot(3, 2, 5)
scatter_by_features(1, 3)
 
# feature "petal length" and "petal width"
plt.subplot(3, 2, 6)
scatter_by_features(2, 3)
 
plt.tight_layout()
 
plt.show()
 

Irisデータセットではあやめの種類が分かっているので,クラスタ分析の結果と照らし合わせて見ることができます.
上記のコードに,以下を追記することで混同行列を見ることができます.

from sklearn import metrics

print(metrics.confusion_matrix(iris['target'], model.labels_))
print(iris['target_names'])
 
実行結果は以下のようになります.
[[ 0 50  0]
 [48  0  2]
 [14  0 36]]
 
['setosa' 'versicolor' 'virginica']
 
この混同行列の結果からすると,versicolorは完全に分類ができていますが,setosaとvirginicaは一部が混ざっているという結果となっています.