k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (2024)

Learning Blog
  • ホーム
  • ディープラーニング
    • ディープラーニング入門
    • コンピュータビジョン
    • 時系列予測
    • AIフレームワーク
    • 深層生成モデル
    • 汎化性能向上のためのテクニック
  • 機械学習
    • 教師あり学習
    • 教師なし学習
    • 探索・推論
    • 線形回帰
    • 分類
    • データ準備
  • Learning Blog
    • R言語入門
    • Python入門
    • Python for Machine Learning
  • 資格・検定
    • E資格とは
    • E資格合格体験記
    • JDLA「E資格」向け認定プログラム
    • JDLA「G検定」向け対策プログラム
  • G検定(AI・機械学習)用語集

    k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (2)

  • ホーム
  • ディープラーニング
    • ディープラーニング入門
    • コンピュータビジョン
    • 時系列予測
    • AIフレームワーク
    • 深層生成モデル
    • 汎化性能向上のためのテクニック
  • 機械学習
    • 教師あり学習
    • 教師なし学習
    • 探索・推論
    • 線形回帰
    • 分類
    • データ準備
  • Learning Blog
    • R言語入門
    • Python入門
    • Python for Machine Learning
  • 資格・検定
    • E資格とは
    • E資格合格体験記
    • JDLA「E資格」向け認定プログラム
    • JDLA「G検定」向け対策プログラム
  • G検定(AI・機械学習)用語集

ホーム 機械学習 教師なし学習

  • 長塚 一真

  • 機械学習

  • 2024.6.13

k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (3)

\ シェア /

Contents

  • k平均法とは
    • k平均法のアルゴリズム
    • k平均法の利点、欠点
  • k平均法の実装
    • sklearnによるデータセットの作成
  • エルボー法による最適なkの決定
    • エルボー法とはどんな手法なのか
    • エルボー法の実装
  • まとめ

k平均法とは

k平均法(k-means)は機械学習のアルゴリズムの一つです。簡単に説明すると、k-means法とは互いに近いデータ同士は同じクラスタに属するという考えに基づき、データ群をk個に分類するクラスタリング(分類)手法です。これだけではいまいちイメージがわかないと思いますので、より詳しく見ていきましょう。

k平均法のアルゴリズム

では、k-meansのアルゴリズムについて見ていきましょう。以下のようなクラスタリングされていないデータを分類したいとします。

k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (4)

ここで、まずそれぞれの点をランダムにクラス分けします。

k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (5)

次に、各クラスについて、重心を計算します。

k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (6)

そして、各点について、計算した重心からの距離を計算し、距離が一番近いクラスタに割り当てなおします。

重心の計算と割り当てなおしのプロセスを、各点の割り当てられるクラスタが変化しなくなるまで繰り返します。こうすることで、最終的には完全に分類された状態となります。

k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (7)

これがk-meansです。また、k-meansはクラスタリングの手法ですので、教師なし学習に分類されます。

k平均法の利点、欠点

k-meansでは重心からの距離のみを計算すればよいため、計算量が少なく済むという利点があります。一方、最初の重心の位置によっては計算量が変動するため、初期値に依存するというデメリットがあります。また、いくつのクラスタに分類するのか、ということはあらかじめ決めておかなければいけません。

k平均法の実装

sklearnによるデータセットの作成

今回はscikit-learnのmake_blobsを用いて作成したデータを用いることにします。make_blobsを用いることでガウス分布に従って生成されるクラスタリング用のデータ群を作成することができます。

'); var blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor = ace.edit("blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor"); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.$blockScrolling = Infinity; blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.resize(true); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.setTheme("ace/theme/monokai"); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.setShowPrintMargin(false); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.getSession().setMode("ace/mode/python"); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.getSession().setUseWrapMode(true); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.getSession().setTabSize(0); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.getSession().setValue('from sklearn.datasets import make_blobs\nimport matplotlib.pyplot as plt\n\nX, y = make_blobs(n_samples = 150, #データの数\n n_features = 2, #特徴量の数\n centers = 3, #データ点分布の中心の数\n cluster_std = 0.8, #ガウス分布のパラメータ\n shuffle = True,\n random_state = 0)\n\nplt.scatter(X[:,0], X[:,1])\nplt.grid()\nplt.show()'); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.setFontSize(14); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.setOptions({ maxLines: 10, minLines: 5 }); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.getSession().on('change', function () { // コードの変更から1秒後に保存 var defaultHeight = blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.getSession().getScreenLength() * blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.renderer.lineHeight + blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.renderer.scrollBar.getWidth(); if (defaultHeight > 200) { $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").parents('.item').show(); } else { $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").parents('.item').hide(); } // delay(function() { // save_usercode(1, 'block_bdde5779ba3232ee97bcae986acdb181'); // }, 1000); }); // エディターの高さ調整 var heightUpdateFunction = function() { if (!$("#blockblock_bdde5779ba3232ee97bcae986acdb181_tab1").hasClass("hidden")) { var defaultHeight = blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.getSession().getScreenLength() * blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.renderer.lineHeight + blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.renderer.scrollBar.getWidth(); if ($("#tab_blockblock_bdde5779ba3232ee97bcae986acdb181_tab1").hasClass("active2")) { $("#blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor").removeClass("editor-close"); } if (defaultHeight > 200 && $("#blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor").hasClass("editor-close")) { $("#blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor").removeClass("editor-close"); $("#tab_blockblock_bdde5779ba3232ee97bcae986acdb181_tab1").removeClass("active2"); $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").find('.mes').text('閉じる'); $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").find('i').removeClass('fa-expand'); $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").find('i').addClass('fa-compress'); $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").parents('.content_body').removeClass("more"); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.setOptions({ maxLines: Infinity, minLines: 5 }); } else { $("#blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor").addClass("editor-close"); $("#tab_blockblock_bdde5779ba3232ee97bcae986acdb181_tab1").removeClass("active2"); $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").find('.mes').text('開く'); $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").find('i').addClass('fa-expand'); $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").find('i').removeClass('fa-compress'); blockblock_bdde5779ba3232ee97bcae986acdb181_tab1_editor.setOptions({ maxLines: 10, minLines: 5 }); } // コード開閉ボタン非表示 if (defaultHeight > 200) { $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").parents('.item').show(); } else { $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").parents('.item').hide(); } } }; $(document).ready(function ($){ if (!$("#blockblock_bdde5779ba3232ee97bcae986acdb181_tab1").hasClass("hidden")) { heightUpdateFunction(); } }); $("#show-blockblock_bdde5779ba3232ee97bcae986acdb181_editor").on('click', heightUpdateFunction); } }); })(jQuery);

 k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (8)

ここではガウス分布中心の数を3つに設定しましたが、実際に分布はだいたい3つにクラス分けできそうであることが見て取れると思います。また、yは正解ラベルですが今回は教師なし学習ですので学習には用いません。

データの形状も確認してみましょう。

'); var blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor = ace.edit("blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor"); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.$blockScrolling = Infinity; blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.resize(true); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.setTheme("ace/theme/monokai"); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.setShowPrintMargin(false); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.getSession().setMode("ace/mode/python"); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.getSession().setUseWrapMode(true); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.getSession().setTabSize(0); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.getSession().setValue('from sklearn.datasets import make_blobs\n\nX, y = make_blobs(n_samples = 150, #データの数\n n_features = 2, #特徴量の数\n centers = 3, #データ点分布の中心の数\n cluster_std = 0.8, #ガウス分布のパラメータ\n shuffle = True,\n random_state = 0)\n\nprint(X.shape)\nprint(y.shape)'); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.setFontSize(14); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.setOptions({ maxLines: 10, minLines: 5 }); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.getSession().on('change', function () { // コードの変更から1秒後に保存 var defaultHeight = blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.getSession().getScreenLength() * blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.renderer.lineHeight + blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.renderer.scrollBar.getWidth(); if (defaultHeight > 200) { $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").parents('.item').show(); } else { $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").parents('.item').hide(); } // delay(function() { // save_usercode(1, 'block_08e01a68f07f2c535cbcd5d13d4d19a7'); // }, 1000); }); // エディターの高さ調整 var heightUpdateFunction = function() { if (!$("#blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1").hasClass("hidden")) { var defaultHeight = blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.getSession().getScreenLength() * blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.renderer.lineHeight + blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.renderer.scrollBar.getWidth(); if ($("#tab_blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1").hasClass("active2")) { $("#blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor").removeClass("editor-close"); } if (defaultHeight > 200 && $("#blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor").hasClass("editor-close")) { $("#blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor").removeClass("editor-close"); $("#tab_blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1").removeClass("active2"); $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").find('.mes').text('閉じる'); $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").find('i').removeClass('fa-expand'); $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").find('i').addClass('fa-compress'); $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").parents('.content_body').removeClass("more"); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.setOptions({ maxLines: Infinity, minLines: 5 }); } else { $("#blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor").addClass("editor-close"); $("#tab_blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1").removeClass("active2"); $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").find('.mes').text('開く'); $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").find('i').addClass('fa-expand'); $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").find('i').removeClass('fa-compress'); blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1_editor.setOptions({ maxLines: 10, minLines: 5 }); } // コード開閉ボタン非表示 if (defaultHeight > 200) { $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").parents('.item').show(); } else { $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").parents('.item').hide(); } } }; $(document).ready(function ($){ if (!$("#blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_tab1").hasClass("hidden")) { heightUpdateFunction(); } }); $("#show-blockblock_08e01a68f07f2c535cbcd5d13d4d19a7_editor").on('click', heightUpdateFunction); } }); })(jQuery);
 

データの個数は150個、その特徴量の数は2つに設定してあります。

では、データの準備ができたので早速k-meansを実装していきましょう。

まず、各点のクラスを適当に初期化する処理を実装します。

n_clusters = k #クラスタ数def init(X): #0からクラスタ数-1までの整数からランダムにラベルを振る labels_ = np.random.randint(0, n_clusters, X.shape[0]) #ラベルの変動確認用の配列 old_labels = np.ones(X.shape[0]) * -1 cluster_centers_ = np.zeros((n_clusters, X.shape[1])) #クラスタの重心を格納する配列 dist = np.zeros((X.shape[0], n_clusters)) #距離を格納する配列

ランダムなラベルの配列、重心格納用の配列、距離の格納用の配列を初期化します。ここで、old_labelsは次のループ処理においてループを抜ける条件のための配列です。

次に、重心の計算処理、ラベルの更新処理を実装しましょう。ループはラベルの変動がなくなるまで繰り返します。距離は各重心について全ての点との距離を計算するということに注意してください。

def predict(X): #ラベルの変動がなくなるまで繰り返す while (not(labels_ == old_labels).all() ): for i in range(n_clusters): X_clusterd = X[labels_ == i,:] #ラベルに応じたXを抜き出す #個数方向の平均として抜き出したXの重心を計算 cluster_centers_[i,:] = X_clusterd.mean(axis = 0) #全ての点について、重心からの距離を計算 for i, x in enumerate(X): for j, center in enumerate(cluster_centers_): dist[i][j] = math.sqrt(distance_2(x, center)) #更新前のラベルを保存 old_labels = labels_ #最も重心との距離が近かったクラスにラベルを更新 labels_ = dist.argmin(axis = 1) return labels_#ユークリッド距離の計算def distance_2(self, p, q): return (p[0] - q[0])**2 + (p[1] - q[1])**2

ここで、空のクラスができてしまったときのことを考慮する必要があります。上のコードでは要素数が0のクラスができたとき、重心が計算されません。ここで場合分けをする必要があります。これらを踏まえて、k-meansモデルの実装は以下のようになります。

import numpy as npimport mathclass KMeans2D(): def __init__(self, k=3): self.n_clusters = k #0からクラスタ数-1までの整数からランダムにラベルを振る self.labels_ = np.random.randint(0, self.n_clusters, X.shape[0]) #ラベルの変動確認用の配列 self.old_labels = np.ones(X.shape[0]) * -1 self.cluster_centers_ = np.zeros((self.n_clusters, X.shape[1])) #クラスタの重心を格納する配列 self.dist = np.zeros((X.shape[0], self.n_clusters)) #距離を格納する配列 self.dist_2 =np.zeros(X.shape[0]) #空のクラスタができたときの対策用 def predict(self, X): #ラベルの変動がなくなるまで繰り返す while (not(self.labels_ == self.old_labels).all() ): for i in range(self.n_clusters): X_clusterd = X[self.labels_ == i,:] #ラベルに応じたXを抜き出す #個数方向の平均として抜き出したXの重心を計算 if X_clusterd.size != 0: #空のクラスタじゃないときはそのまま重心計算 self.cluster_centers_[i,:] = X_clusterd.mean(axis = 0) else : #空のクラスタが存在する場合はそのときの重心から最も遠い点を新たな重心に設定 for n, x in enumerate(X): self.dist_2[n] = self.distance_2(x,self.cluster_centers_[i,:]) self.cluster_centers_[i,:] = X[self.dist_2.argmax(axis = 0)] #全ての点について、重心からの距離を計算 for i, x in enumerate(X): for j, center in enumerate(self.cluster_centers_): self.dist[i][j] = math.sqrt(self.distance_2(x, center)) #更新前のラベルを保存 self.old_labels = self.labels_ #最も重心との距離が近かったクラスにラベルを更新 self.labels_ = self.dist.argmin(axis = 1) return self.labels_ def distance_2(self, p, q): return (p[0] - q[0])**2 + (p[1] - q[1])**2 

では、実際にクラスタリング処理を行ってみましょう。

'); var blockblock_7d900e0179bb7133750825da16415465_tab1_editor = ace.edit("blockblock_7d900e0179bb7133750825da16415465_tab1_editor"); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.$blockScrolling = Infinity; blockblock_7d900e0179bb7133750825da16415465_tab1_editor.resize(true); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.setTheme("ace/theme/monokai"); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.setShowPrintMargin(false); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.getSession().setMode("ace/mode/python"); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.getSession().setUseWrapMode(true); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.getSession().setTabSize(0); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.getSession().setValue('from sklearn.datasets import make_blobs\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport math\n\nX, y = make_blobs(n_samples = 150,\n n_features = 2, \n centers = 3,\n cluster_std = 0.8,\n shuffle = True,\n random_state = 0)\n\n\nclass KMeans2D(): \n def __init__(self, k=3):\n self.n_clusters = k\n #0からクラスタ数-1までの整数からランダムにラベルを振る\n self.labels_ = np.random.randint(0, self.n_clusters, X.shape[0]) \n \n #ラベルの変動確認用の配列\n self.old_labels = np.ones(X.shape[0]) * -1 \n\n self.cluster_centers_ = np.zeros((self.n_clusters, X.shape[1])) #クラスタの重心を格納する配列\n self.dist = np.zeros((X.shape[0], self.n_clusters)) #距離を格納する配列\n self.dist_2 =np.zeros(X.shape[0]) #空のクラスタができたときの対策用\n\n def predict(self, X): \n #ラベルの変動がなくなるまで繰り返す\n while (not(self.labels_ == self.old_labels).all() ):\n\n for i in range(self.n_clusters):\n X_clusterd = X[self.labels_ == i,:] #ラベルに応じたXを抜き出す\n\n #個数方向の平均として抜き出したXの重心を計算\n if X_clusterd.size != 0: #空のクラスタじゃないときはそのまま重心計算\n self.cluster_centers_[i,:] = X_clusterd.mean(axis = 0)\n else :\n #空のクラスタが存在する場合はそのときの重心から最も遠い点を新たな重心に設定\n for n, x in enumerate(X):\n self.dist_2[n] = self.distance_2(x, self.cluster_centers_[i,:]) \n\n self.cluster_centers_[i,:] = X[self.dist_2.argmax(axis = 0)] \n\n #全ての点について、重心からの距離を計算\n for i, x in enumerate(X):\n for j, center in enumerate(self.cluster_centers_):\n self.dist[i][j] = math.sqrt(self.distance_2(x, center))\n\n #更新前のラベルを保存 \n self.old_labels = self.labels_ \n\n #最も重心との距離が近かったクラスにラベルを更新\n self.labels_ = self.dist.argmin(axis = 1)\n\n return self.labels_\n\n def distance_2(self, p, q):\n return (p[0] - q[0])**2 + (p[1] - q[1])**2 \n\n\n\nkm = KMeans2D(k = 3)\n\nlabels = km.predict(X)\n\n#分類結果の描画\nplt.scatter(X[labels == 0, 0], X[labels == 0, 1], s=30, c=\'yellow\', marker=\'o\', label=\'cluster 1\')\nplt.scatter(X[labels == 1, 0], X[labels == 1, 1], s=30, c=\'lightblue\', marker=\'o\', label=\'cluster 2\')\nplt.scatter(X[labels == 2, 0], X[labels == 2, 1], s=30, c=\'lightgreen\', marker=\'o\', label=\'cluster 3\')\n\nplt.legend()\nplt.grid()\nplt.show()'); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.setFontSize(14); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.setOptions({ maxLines: 10, minLines: 5 }); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.getSession().on('change', function () { // コードの変更から1秒後に保存 var defaultHeight = blockblock_7d900e0179bb7133750825da16415465_tab1_editor.getSession().getScreenLength() * blockblock_7d900e0179bb7133750825da16415465_tab1_editor.renderer.lineHeight + blockblock_7d900e0179bb7133750825da16415465_tab1_editor.renderer.scrollBar.getWidth(); if (defaultHeight > 200) { $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").parents('.item').show(); } else { $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").parents('.item').hide(); } // delay(function() { // save_usercode(1, 'block_7d900e0179bb7133750825da16415465'); // }, 1000); }); // エディターの高さ調整 var heightUpdateFunction = function() { if (!$("#blockblock_7d900e0179bb7133750825da16415465_tab1").hasClass("hidden")) { var defaultHeight = blockblock_7d900e0179bb7133750825da16415465_tab1_editor.getSession().getScreenLength() * blockblock_7d900e0179bb7133750825da16415465_tab1_editor.renderer.lineHeight + blockblock_7d900e0179bb7133750825da16415465_tab1_editor.renderer.scrollBar.getWidth(); if ($("#tab_blockblock_7d900e0179bb7133750825da16415465_tab1").hasClass("active2")) { $("#blockblock_7d900e0179bb7133750825da16415465_tab1_editor").removeClass("editor-close"); } if (defaultHeight > 200 && $("#blockblock_7d900e0179bb7133750825da16415465_tab1_editor").hasClass("editor-close")) { $("#blockblock_7d900e0179bb7133750825da16415465_tab1_editor").removeClass("editor-close"); $("#tab_blockblock_7d900e0179bb7133750825da16415465_tab1").removeClass("active2"); $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").find('.mes').text('閉じる'); $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").find('i').removeClass('fa-expand'); $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").find('i').addClass('fa-compress'); $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").parents('.content_body').removeClass("more"); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.setOptions({ maxLines: Infinity, minLines: 5 }); } else { $("#blockblock_7d900e0179bb7133750825da16415465_tab1_editor").addClass("editor-close"); $("#tab_blockblock_7d900e0179bb7133750825da16415465_tab1").removeClass("active2"); $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").find('.mes').text('開く'); $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").find('i').addClass('fa-expand'); $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").find('i').removeClass('fa-compress'); blockblock_7d900e0179bb7133750825da16415465_tab1_editor.setOptions({ maxLines: 10, minLines: 5 }); } // コード開閉ボタン非表示 if (defaultHeight > 200) { $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").parents('.item').show(); } else { $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").parents('.item').hide(); } } }; $(document).ready(function ($){ if (!$("#blockblock_7d900e0179bb7133750825da16415465_tab1").hasClass("hidden")) { heightUpdateFunction(); } }); $("#show-blockblock_7d900e0179bb7133750825da16415465_editor").on('click', heightUpdateFunction); } }); })(jQuery);
 k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (9)

上手く分類することが確認できたのではないかと思います。上記のプログラムは、点の描画については3クラスまでしか対応していませんが、分類自体はクラスタ数が増えても対応できます。また、3クラスまでは対応しているので、k=2やk=1のときを試してみてください。

エルボー法による最適なkの決定

ここまでの実装ではkの数を事前に決める必要がありました。この最適なkの値はなんとなくの目視や試行錯誤で決定するのもよいですが、エルボー法という手法でも決定することができます。

エルボー法とはどんな手法なのか

エルボー法では、まず以下に示す式に従い、残差平方和(SSEもしくはRSS)という値を算出します。残差平方和は簡単に言うと、データと予測値との差異を評価する尺度です。ということは、低ければ低いほどいいということですよね。残差平方和は以下の式で与えられます。ここでyiはデータ、f(xi)は予測値、nはデータ総数です。

k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (10)

これをk平均法におけるkの数を増やしながら計算し、横軸をクラスタ数、縦軸をSSEとしてグラフにとると以下のようなグラフが得られます。

k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (11)

このグラフにおいて、あきらかにk=3のところがいいSSE値とそうでない値との境界となっていることがわかります。このときのkこそが最適なkである、という様に決定します。これがエルボー法です。

エルボー法の実装

では、エルボー法を実装してみましょう。実装とはいっても、ここまでのk-meansにSSEの計算とそのグラフの描画を加えるだけです。

'); var blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor = ace.edit("blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor"); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.$blockScrolling = Infinity; blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.resize(true); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.setTheme("ace/theme/monokai"); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.setShowPrintMargin(false); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.getSession().setMode("ace/mode/python"); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.getSession().setUseWrapMode(true); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.getSession().setTabSize(0); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.getSession().setValue('from sklearn.datasets import make_blobs\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport math\n\nX, y = make_blobs(n_samples = 150,\n n_features = 2, \n centers = 3,\n cluster_std = 0.8,\n shuffle = True,\n random_state = 0)\n\n\nclass KMeans2D_2(): \n def __init__(self, k=3):\n self.n_clusters = k\n self.labels_ = np.random.randint(0, self.n_clusters, X.shape[0]) \n \n self.old_labels = np.ones(X.shape[0]) * -1 \n\n self.cluster_centers_ = np.zeros((self.n_clusters, X.shape[1])) \n self.dist = np.zeros((X.shape[0], self.n_clusters)) \n self.dist_2 =np.zeros(X.shape[0])\n\n def predict(self, X): \n while (not(self.labels_ == self.old_labels).all() ):\n\n self.inertia_ = 0 #SSE\n\n for i in range(self.n_clusters):\n X_clusterd = X[self.labels_ == i,:] \n\n if X_clusterd.size != 0: \n self.cluster_centers_[i,:] = X_clusterd.mean(axis = 0)\n else :\n for n, x in enumerate(X):\n self.dist_2[n] = self.distance_2(x,self.cluster_centers_[i,:]) \n\n self.cluster_centers_[i,:] = X[self.dist_2.argmax(axis = 0)] \n\n for x in X_clusterd: #抜き出したXについて、SSE計算\n self.inertia_ += self.distance_2(x, self.cluster_centers_[i,:])\n\n for i, x in enumerate(X):\n for j, center in enumerate(self.cluster_centers_):\n self.dist[i][j] = math.sqrt(self.distance_2(x, center))\n\n self.old_labels = self.labels_ \n\n self.labels_ = self.dist.argmin(axis = 1)\n return self.labels_\n\n def distance_2(self, p, q):\n return (p[0] - q[0])**2 + (p[1] - q[1])**2 \n\n#グラフの描画\nmax_k = 10\ndistortions = []\n\nfor k in range(1, max_k):\n km2 = KMeans2D_2(k)\n labels = km2.predict(X)\n print(f"k = {k}, km2.inertia_ = {km2.inertia_}")\n distortions.append( km2.inertia_ )\n\nplt.plot(range(1, max_k), distortions, marker=\'o\')\nplt.xlabel("k")\nplt.ylabel("SSE")\nplt.show()'); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.setFontSize(14); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.setOptions({ maxLines: 10, minLines: 5 }); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.getSession().on('change', function () { // コードの変更から1秒後に保存 var defaultHeight = blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.getSession().getScreenLength() * blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.renderer.lineHeight + blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.renderer.scrollBar.getWidth(); if (defaultHeight > 200) { $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").parents('.item').show(); } else { $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").parents('.item').hide(); } // delay(function() { // save_usercode(1, 'block_74f88a53ef4f88d96cbe8c1e18e0a6ab'); // }, 1000); }); // エディターの高さ調整 var heightUpdateFunction = function() { if (!$("#blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1").hasClass("hidden")) { var defaultHeight = blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.getSession().getScreenLength() * blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.renderer.lineHeight + blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.renderer.scrollBar.getWidth(); if ($("#tab_blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1").hasClass("active2")) { $("#blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor").removeClass("editor-close"); } if (defaultHeight > 200 && $("#blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor").hasClass("editor-close")) { $("#blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor").removeClass("editor-close"); $("#tab_blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1").removeClass("active2"); $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").find('.mes').text('閉じる'); $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").find('i').removeClass('fa-expand'); $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").find('i').addClass('fa-compress'); $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").parents('.content_body').removeClass("more"); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.setOptions({ maxLines: Infinity, minLines: 5 }); } else { $("#blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor").addClass("editor-close"); $("#tab_blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1").removeClass("active2"); $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").find('.mes').text('開く'); $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").find('i').addClass('fa-expand'); $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").find('i').removeClass('fa-compress'); blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1_editor.setOptions({ maxLines: 10, minLines: 5 }); } // コード開閉ボタン非表示 if (defaultHeight > 200) { $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").parents('.item').show(); } else { $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").parents('.item').hide(); } } }; $(document).ready(function ($){ if (!$("#blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_tab1").hasClass("hidden")) { heightUpdateFunction(); } }); $("#show-blockblock_74f88a53ef4f88d96cbe8c1e18e0a6ab_editor").on('click', heightUpdateFunction); } }); })(jQuery);
 k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (12)

グラフの結果から、今回はk=3のときが最適であるとわかり、これは目視によるなんとなくの分類とも一致しているように思えますね。

まとめ

今回はk-meansについて解説しました。仕組み自体はそれほど複雑なものではないと思いますが、機械学習における代表的な手法の一つですのでよく理解しておきましょう。

\ シェア /

k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (13)

長塚 一真

zero to one でインターンをしている大学生です。大学ではニューロロボティクスの研究をしており、現在はzero to oneでMLやDLについての記事を書いています。AIの技術をロボットに応用することに興味があります。

E資格スピードパッケージ2023#2修了者合格率100%達成

zero to oneの「E資格」向け認定プログラム

日本ディープラーニング協会の実施するE資格の受験ならzero to oneの「E資格」向け認定プログラム (税込165,000円) をおすすめします。当講座は、東京大学大学院工学系研究科の松尾豊教授と東北大学大学院情報科学研究科の岡谷貴之教授が監修する実践的なプログラムとなっています。
厚生労働省の教育訓練給付制度対象のE資格認定プログラムの中では最安値※となり、実質負担額49,500円~(支給割合70%の場合)で受講可能です。※2023年弊社調べ

人工知能基礎講座を提供中

人工知能の第一人者である東京大学の松尾豊教授が監修した人工知能基礎講座を受講してみませんか?人工知能の歴史から自然言語処理、機械学習、深層学習といった最先端のトピックやAIに関わる法律問題まで網羅しているので全てのビジネスパーソン・AIの初学者におすすめです。

サンプル動画

人工知能基礎講座はこちら↓

AI初学者・ビジネスパーソン向けのG検定対策講座

G検定受験前にトレーニングしたい方向けの問題集「G検定実践トレーニング」も提供中です。

Related article

機械学習 動的計画法(DP)をわかりやすく解説【Pythonコード付き】 吉崎 響 2024.6.7 機械学習 機械学習によく出てくる「スカラー・行列・テンソル」とは? 大柴 徹弥 2024.5.15 機械学習 【SVM】サポートベクターマシーン(SVM)の仕組みをわかりやすく解説 阿知葉 大樹 2023.9.26 機械学習 k近傍法(knn)をわかりやすく Python を用いて基本から実装まで解説 長塚 一真 2023.8.2
k平均法(k-means)アルゴリズムをわかりやすく解説【Pythonコード有】 (2024)
Top Articles
Latest Posts
Article information

Author: Errol Quitzon

Last Updated:

Views: 5933

Rating: 4.9 / 5 (79 voted)

Reviews: 94% of readers found this page helpful

Author information

Name: Errol Quitzon

Birthday: 1993-04-02

Address: 70604 Haley Lane, Port Weldonside, TN 99233-0942

Phone: +9665282866296

Job: Product Retail Agent

Hobby: Computer programming, Horseback riding, Hooping, Dance, Ice skating, Backpacking, Rafting

Introduction: My name is Errol Quitzon, I am a fair, cute, fancy, clean, attractive, sparkling, kind person who loves writing and wants to share my knowledge and understanding with you.