Main Content

k-means クラスタリング

このトピックでは k-means クラスタリングを紹介し、Statistics and Machine Learning Toolbox™ の関数 kmeans を使用してデータ セットに最適なクラスタリング ソリューションを見つける例について説明します。このトピックでは、Scikit-learn Model Predict ブロックを使用した Simulink® での k-means クラスタリングの実装例も示します。

k-means クラスタリングの紹介

k-means クラスタリングは、分割を行うための方法です。関数 kmeans は、データを互いに排他的な k 個のクラスターに分割し、各観測値が割り当てられたクラスターのインデックスを返します。kmeans は、データ内の各観測値を、空間内のある位置をもつオブジェクトとして処理します。この関数は、各クラスター内のオブジェクトができるだけ互いに近くにあり、他のクラスターのオブジェクトからはできるだけ遠くにあるような分割を探します。距離計量を選択して、データの属性に基づいて kmeans と一緒に使用することができます。多くのクラスタリング手法と同じように、k-means クラスタリングでもクラスタリングを行う前にクラスター数 k を指定しなければなりません。

階層クラスタリングと異なり、k-means クラスタリングは、データ内の観測値の各ペア間の非類似度ではなく、実際の観測値で動作します。また、k-means クラスタリングでは、クラスターの多重レベルの階層ではなく、単一レベルのクラスターを作成します。そのため、k-means クラスタリングは、大量のデータを処理する場合に階層クラスタリングより適していることが多くあります。

k-means の分割内の各クラスターは、メンバー オブジェクトと重心 (または中心) で構成されます。各クラスター内で、kmeans は、重心とクラスターのすべてのメンバー オブジェクトの間の合計距離を最小化します。kmeans は、サポートされている距離計量の重心クラスターを異なる方法で計算します。詳細については、'Distance'を参照してください。

kmeans で使用可能な名前と値のペアの引数を使用して、最小化の詳細を制御できます。たとえば、クラスターの重心の初期値や、アルゴリズムの最大反復回数を指定できます。既定では、kmeans はクラスターの重心を初期化するために k-means++ アルゴリズムを使用し、距離を決定するために 2 乗ユークリッド距離計量を使用します。

k-means クラスタリングを実行するときは、次のベストプラクティスに従ってください。

  • k のさまざまな値について k-means クラスタリングの解を比較して、自分のデータにとって最適なクラスター数を決定する。

  • シルエット プロットとシルエット値を調べることでクラスタリングの解を評価する。また、関数 evalclusters を使用して、ギャップ値、シルエット値、Davies-Bouldin インデックス値、Calinski-Harabasz インデックス値などの基準に基づいてクラスタリングの解を評価することもできます。

  • 無作為に選択したさまざまな重心からのクラスタリングを複製し、すべての複製の中で距離の総和が最小である解を返す。

k-means クラスタリングを対話的に実行するには、[データのクラスタリング] ライブ エディター タスクを使用します。

k-means クラスタリングの解の比較

この例では、4 次元のデータ セットに対する k-means クラスタリングについて確認します。この例では、シルエット プロットやシルエット値を使用することでデータ セットに適切なクラスター数を決定して、さまざまな k-means クラスタリングの解の結果を分析する方法を示します。また、名前と値のペアの引数 'Replicates' を使用して、可能性のある解を指定された数だけテストして、距離の総和が最小である解を返す方法も示します。

データ セットの読み込み

kmeansdata データ セットを読み込みます。

rng('default')  % For reproducibility
load('kmeansdata.mat')
size(X)
ans = 1×2

   560     4

データ セットは 4 次元であり、可視化は容易ではありません。ただし、kmeans を使用してデータ中にグループ構造があるかどうかを調べることができます。

クラスターの作成と間隔の確認

k-means クラスタリングを使用して、データ セットを 3 つのクラスターに分割します。尺度として市街地距離を指定し、既定の k-means++ アルゴリズムをクラスター中心の初期化に使用します。'Display' 名前と値のペアの引数を使用して、その解に最終的な距離の合計を表示します。

[idx3,C,sumdist3] = kmeans(X,3,'Distance','cityblock', ...
    'Display','final');
Replicate 1, 7 iterations, total sum of distances = 2459.98.
Best total sum of distances = 2459.98

idx3 には、X の各行に対するクラスター割り当てを示すクラスター インデックスが含まれています。生成されたクラスターが十分に分離されているかどうかを確認するには、シルエット プロットを作成できます。

シルエット プロットは、1 個のクラスター中の各点が近隣のクラスター中の点にどれくらい接近しているかについて基準を表示します。この尺度の範囲は、(隣接するクラスターから非常に遠い点を示す) 1 から、(いずれかのクラスター内に含まれているかどうかがあいまいな点を示す) 0 を通り、(適切でないクラスターに割り当てられた確率が高い点を示す) –1 までです。silhouetteは、その 1 番目の出力でこれらの値を返します。

idx3 からシルエット プロットを作成します。k-means クラスタリングが差の絶対値の総和に基づくことを指示するために、距離計量に対して 'cityblock' を指定します。

[silh3,h] = silhouette(X,idx3,'cityblock');
xlabel('Silhouette Value')
ylabel('Cluster')

シルエット プロットは、2 番目のクラスターの大部分の点が (0.6 よりも大きい) 大きなシルエット値をもっており、そのクラスターが近隣のクラスターからある程度分離されていることを示しています。一方、3 番目のクラスターにはシルエット値の低い点が多く含まれており、また 1 番目と 3 番目のクラスターには負の値をもつ点がいくつか含まれていることから、これら 2 つのクラスターが十分には分離されていないことがわかります。

kmeans がデータのより適切なグループ化を見つけることができるかどうか確認するには、クラスターの数を 4 に増やします。'Display' 名前と値のペアの引数を使用して、各反復に関する情報を表示します。

idx4 = kmeans(X,4,'Distance','cityblock','Display','iter');
  iter	 phase	     num	         sum
     1	     1	     560	     1792.72
     2	     1	       6	      1771.1
Best total sum of distances = 1771.1

4 つのクラスターのシルエット プロットを作成します。

[silh4,h] = silhouette(X,idx4,'cityblock');
xlabel('Silhouette Value')
ylabel('Cluster')

シルエット プロットは、これら 4 つのクラスターが、前の解の 3 つのクラスターよりも、より適切に分離されていることを示します。2 つの場合については、平均のシルエット値を計算することで、2 つの解をより定量的に比較できます。

平均のシルエット値を計算します。

cluster3 = mean(silh3)
cluster3 = 0.5352
cluster4 = mean(silh4)
cluster4 = 0.6400

4 つのクラスターの平均シルエット値は、3 つのクラスターの平均値よりも高くなります。こうした値は、シルエット プロットに示された結果を立証しています。

最後に、データ内で 5 つのクラスターを見つけます。シルエット プロットを作成し、5 つのクラスターの平均シルエット値を計算します。

idx5 = kmeans(X,5,'Distance','cityblock','Display','final');
Replicate 1, 7 iterations, total sum of distances = 1647.26.
Best total sum of distances = 1647.26
[silh5,h] = silhouette(X,idx5,'cityblock');
xlabel('Silhouette Value')
ylabel('Cluster')

mean(silh5)
ans = 0.5721

このシルエット プロットは、2 つのクラスターにはほとんど低いシルエット値をもつ点が含まれ、5 番目のクラスターには負の値をもつ点がいくつか含まれているため、5 がおそらくクラスターの正しい数ではないことを示しています。また、5 つのクラスターの平均シルエット値は 4 つのクラスターの値よりも低くなります。どれだけ多くのクラスターがデータ内にあるかについて分からない場合、ある範囲内の複数の k の値 (クラスターの数) を使用して実験することをお勧めします。

クラスターの数が増加すると、距離の合計が減少することに注意してください。たとえば、クラスターの数が 3 から 4、そして 5 に増加すると、距離の合計は 2459.98 から 1771.1、そして 1647.26 に減少します。そのため、距離の合計は最適なクラスター数を決定するのに役立ちません。

ローカルな最小値の回避

既定の設定では、関数 kmeans は無作為に選択された初期の重心位置の集合を使用して処理を開始します。kmeans アルゴリズムは、ローカルな (グローバルではない) 最小値である解に収束する可能性があります。つまり、kmeans は、任意の 1 点を異なるクラスターに移動すると距離の総和が大きくなるようにデータを分割する可能性があります。ただし、他の多くのタイプの数値的最小化と同様に、kmeans によって到達する解は、開始点に依存する場合があります。したがって、そのデータについて、距離の総和がより小さい他の解 (ローカルな最小値) が存在する可能性があります。'Replicates' 名前と値のペアの引数を使用して異なる解をテストできます。複数の複製を指定すると、kmeans は、無作為に選択した重心から始まるクラスタリング プロセスを各複製について繰り返し、すべての複製の中で距離の総和が最小である解を返します。

データ内の 4 つのクラスターを見つけて、クラスタリングを 5 回複製します。また、尺度として市街地距離を指定し、'Display' 名前と値のペアの引数を使用して、それぞれの解に最終的な距離の合計を表示します。

[idx4,cent4,sumdist] = kmeans(X,4,'Distance','cityblock', ...
    'Display','final','Replicates',5);               
Replicate 1, 2 iterations, total sum of distances = 1771.1.
Replicate 2, 3 iterations, total sum of distances = 1771.1.
Replicate 3, 3 iterations, total sum of distances = 1771.1.
Replicate 4, 6 iterations, total sum of distances = 2300.23.
Replicate 5, 2 iterations, total sum of distances = 1771.1.
Best total sum of distances = 1771.1

複製 4 で、kmeans はローカルな最小値を見つけます。複製はそれぞれ無作為に選択した異なる重心の初期値から始まっているため、kmeans は 2 つ以上のローカルな最小値を見つける場合があります。しかし、kmeans によって返される最終的な解は、すべての複製のうちで距離の総和が最小値となる解になります。

kmeans によって返される最終的な解に対して、点と重心間の距離のクラスター内合計の総和を求めます。

sum(sumdist)
ans = 1.7711e+03

Python Scikit-learn Model Predict ブロックを使用したクラスター割り当ての予測

この例では、Scikit-learn Model Predictブロックを Simulink® での予測に使用する方法を示します。このブロックは、観測値 (予測子データ) を受け入れて、Python® で実行される学習済みの教師なし機械学習モデルを使用することにより、予測されたクラスター割り当てを返します。MATLAB® は、Python の参照実装 (通称 CPython) をサポートします。Mac または Linux® プラットフォームを使用している場合、Python は既にインストールされています。Windows® を使用している場合、"https://www.python.org/downloads/" などにある配布版をインストールする必要があります。詳細については、Python を使用するためのシステムの構成を参照してください。MATLAB Python 環境に scikit-learn モジュールがインストールされている必要があります。

Scikit-learn Model Predict ブロックには、pickle.dump()joblib.dump()、または skops.io.dump() を使用して Python で保存した事前学習済みの scikit-learn™ モデル ファイルが必要です。Python の対応する predict() メソッドがモデルでサポートされていなければなりません。この例では、保存されたモデル sklearnmodel.pkl が用意されています。これは、標準化されたフィッシャーのアヤメのデータで学習させた MiniBatchKMeans クラスタリング モデルを scikit-learn version 1.3.2 の pickle.dump() で保存したものです。また、この例では Python ファイル scaler.pklsklearnmodel.py、および preprocessor.py も用意されています。

用意されている Simulink モデルを開く

この例では、Scikit-learn Model Predict ブロックを含む Simulink モデル slexScikitLearnPredictExample.slx が用意されています。この Simulink モデルを開くことも、次のセクションの説明に従って新しいモデルを作成することもできます。

Simulink モデル slexScikitLearnPredictExample.slx を開きます。

open_system("slexScikitLearnPredictExample");

Simulink モデルを開くと、Simulink モデルを読み込む前に、ソフトウェアがコールバック関数 PreLoadFcn のコードを実行します。slexScikitLearnPredictExample のコールバック関数 PreLoadFcn には、学習済みモデルの変数 modelInput がワークスペースにあるかどうかをチェックするコードが含まれています。ワークスペースに変数がない場合、PreLoadFcn は標本データを読み込み、Simulink モデルの入力信号を作成します。コールバック関数を表示するには、[モデル化] タブの [設定] セクションで、[モデル設定] をクリックし、[モデル プロパティ] を選択します。次に、[コールバック] タブで、[モデルのコールバック] ペインのコールバック関数 PreLoadFcn を選択します。

Simulink モデルの作成

新しい Simulink モデルを作成するには、[空のモデル] テンプレートを開き、Statistics and Machine Learning Toolbox™ ライブラリから Scikit-learn Model Predict ブロックを追加します。Inport ブロックと Outport ブロックを追加して、それらを Scikit-learn Model Predict ブロックに接続します。

Scikit-learn Model Predict ブロックをダブルクリックして、[ブロック パラメーター] ダイアログ ボックスを開きます。[scikit-learn モデル ファイルへのパス] テキスト ボックスに「sklearnmodel.pkl」と入力します。

[入力] タブで、Python データ型を float に設定します。[前/後処理] タブで、[preprocess() を定義する Python ファイルへのパス] テキスト ボックスに「preprocessor.py」と入力します。[OK] をクリックします。

4 個の予測子変数をもつデータ セットを使用して Python モデルに学習させたため、Scikit-learn Model Predict ブロックには 4 個の予測子の値を含む観測値が必要です。Inport ブロックをダブルクリックし、[信号属性] タブで [端子の次元] を 4 に設定します。出力信号を入力信号と同じ長さに指定するには、[実行] タブで [サンプル時間] を 1 に設定します。[データを内挿する] チェック ボックスをオフにし、[OK] をクリックします。

フィッシャーのアヤメのデータ セットを読み込みます。これには、150 個の観測値と 4 個の予測子が格納されています。Python モデルの学習に使用したものとは異なる新しい観測値をシミュレートするには、観測値にランダムなガウス ノイズを追加します。

rng(0,"twister") % For reproducibility
load fisheriris
meas = meas + 0.1*randn(size(meas));

入力データ用に適切な構造体配列を作成します。詳細については、Control How Models Load Input Data (Simulink)を参照してください。

modelInput.time = (1:size(meas,1))'-1;
modelInput.signals.values = meas;
modelInput.signals.dimensions = size(meas,2);

ワークスペースから信号データをインポートするには、次を実行します。

  1. [モデル化] タブで、[モデル設定] をクリックして [コンフィギュレーション パラメーター] ダイアログ ボックスを開きます。

  2. [コンフィギュレーション パラメーター] ダイアログ ボックスの左側で、[データのインポート/エクスポート] をクリックします。次に、[入力] チェック ボックスをオンにし、隣のテキスト ボックスに「modelInput」と入力します。

  3. 左側で [ソルバー] をクリックします。[シミュレーション時間] で、[終了時間]size(meas,1)-1 に設定します。[ソルバーの選択] で、[タイプ]Fixed-step に、[ソルバー]discrete (no continuous states) に設定。[OK] をクリックします。

詳細は、シミュレーションのための信号データの読み込み (Simulink)を参照してください。

モデルを Simulink で slexScikitLearnPredictExample.slx として保存します。

Simulink モデルのシミュレート

Simulink モデルをシミュレートして、入力観測値のクラスター割り当てを予測します。Python のインストールで version 1.3.2 より前の scikit-learn を使用している場合、警告メッセージが表示されることがあります。

simOut=sim("slexScikitLearnPredictExample");

Inport ブロックは、観測値を検出すると、それらを Scikit-learn Model Predict ブロックに配置します。Scikit-learn Model Predict ブロックは、予測子データを Python または NumPy のデータ型に変換します。[ブロック パラメーター] ダイアログ ボックスの [入力] タブにある [Python Datatype] 列で指定されたデータ型です。ブロックがデータを Python に渡すと、preprocessor.py で定義されている関数を使用してデータが標準化され、次にデータが Python モデルに送られます。観測値の予測されたクラスター割り当てが Python モデルから返されます。シミュレーション データ インスペクター (Simulink)を使用して、Outport ブロックのログ データを表示できます。

モデル予測の可視化

3 番目の予測子変数と 2 番目の予測子変数の散布図を作成します。モデルによって予測されたクラスター割り当てごとに異なる色を割り当てます。

C = squeeze(simOut.yout.getElement(1).Values.Data);
gscatter(meas(:,2),meas(:,3),C')

Figure contains an axes object. The axes object contains 3 objects of type line. One or more of the lines displays its values using only markers These objects represent 0, 1, 2.

参考

| |

関連するトピック