データサイエンティストによる統計入門 ― k平均法でデータをクラスタリングしてみよう!

ビッグデータ、データサイエンス、人工知能など、統計学を主軸においた分野が隆盛ですが、統計学には高いハードルを感じる方も少なくないでしょう。k平均法を実際に手を動かしながら理解することで、データ分析を身近に感じることができます。

データサイエンティストによる統計入門 ― k平均法でデータをクラスタリングしてみよう!

はじめまして、藤井健人@studiesと申します。イタンジ株式会社でデータ基盤周りの運用を担当しています。

「ビッグデータ」「データサイエンス」「人工知能」といったバズワードに代表されるように、統計学を主軸においた分野の隆盛が日常となって久しいです。

しかし「統計学は学問的な要素があり難しい」という印象を持たれやすく、「実務に活かすのはハードルが高い、怖い」と感じる方も少なくないのではないでしょうか。

そういった方を対象に、今回は統計学の手法の一つであるk平均法を学んでいただきたく筆をとりました。

Webエンジニアが統計を学ぶことで、以下のようなメリットが得られると筆者は考えます。

  • エンジニアは、他職種と比較してデータに近いポジションであるため、気軽にデータを取得して、触り、仮説を検証することができる
  • 統計学を駆使することで、より確度の高い開発方向を決める指針が得られる
  • 機械学習といった、統計学を応用した技術の基礎理解に役立つ

本稿は次の3部からなり、ソースコードをもとに手を動かしながら理解を深められる構成となっています。

  1. 理論の概要を学ぶ
  2. ゼロからロジックを実装する
  3. ライブラリを扱ってみる

本稿を通じてテータ分析、ひいては統計学をより身近に感じていただき、実務への心理的な壁を壊す手助けになれば幸いです。

PythonとJupyter Notebookの環境を用意する

分析を始める前に、分析を行う環境を準備しましょう。本稿で動作確認を行ったソフトウェア(Pythonとそのライブラリ)は次の通りです。

ソフトウェア 説明
Python 3.5.1 プログラミング言語
jupyter 1.0.0 データ分析用の実行環境(Jupyter Notebook)
pandas 0.22.0 データ解析を支援するライブラリ(Pandas)
numpy 1.14.0 数値計算用ライブラリ(NumPy)
scikit-learn 0.19.1 機械学習ライブラリ
matplotlib 2.1.0 グラフ描画ライブラリ(Matplotlib)

今回は、データ分析の記録に最適な実行環境Jupyter Notebookを利用して学習を進めていきます。

まずはプロジェクトのディレクトリを作ります。

$ mkdir k_means
$ cd k_means

次に、各種ライブラリをインストールするためのファイルrequirements.txtを該当ディレクトリに置きます。

jupyter==1.0.0
pandas==0.22.0
numpy==1.14.0
scikit-learn==0.19.1
matplotlib==2.1.0

最後にPythonの仮想環境を作った上で、必要なライブラリをインストールして、Jupyter Notebookを起動しましょう。

$ python -m venv env
$ source env/bin/activate
$ pip install -r requirements.txt
$ jupyter notebook

最後のコマンドでブラウザが起動し、Jupyter Notebookが表示されれば準備OKです。

1

Jupyterの起動画面

k平均法によるクラスタリングの概要を理解する

k平均法は、データクラスタリングと言われる分野の手法の一つです。

データクラスタリングとは、データを外的基準なしに似たもの同士をくっつけてクラスター(群れ)に分けてしまうことです。教師なし機械学習に分類され、データマイニングにもよく使われる手法です。

2

クラスタリングのイメージ図。性質が近いデータをひとまとまりにする

実務では、例えばイタンジの場合、賃貸住宅をレコメンドするシーンで利用しています。賃料や敷金といった住宅情報から賃貸住宅をクラスターにまとめ、類似物件としてユーザーに提案する用途です。

5つの座標データ

ここで、k平均法の基本的な考え方を説明します。

k平均法は、データごとの類似点の中心を見つけ出し、データを意味のあるクラスターに分ける手法です。

例えば、手元に次のような5つの座標データがあるものとして、これらを2つのクラスターに分けたいとします。

(1, 1),
(1, 2),
(2, 2),
(4, 5),
(5, 4)

Jupyter Notebookで新しいNotebookを作り、次のコードを入力します。

%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

samples = np.array([
    [1, 1],
    [1, 2],
    [2, 2],
    [4, 5],
    [5, 4]])
df_sa = pd.DataFrame(samples)
plt.grid()
plt.scatter(df_sa[0], df_sa[1], c='blue')
plt.show()

実行すると、次の図がプロットされます。

3

2つのクラスターそれぞれの中心を適当に決め打ちする

ここで2つのクラスターの中心を次のように、適当に決め打ちしてあげましょう。

(1, 1.1),
(1, 2.1)

Notebookに次のように入力します。

centers = np.array([
    [1, 1.1],
    [1, 2.1]])
df_ce = pd.DataFrame(centers)
plt.grid()
plt.scatter(df_sa[0], df_sa[1], c='blue')
plt.scatter(df_ce[0], df_ce[1], c='red')
plt.show()

次の図がプロットされます。

4

中心に近い座標をクラスターに分ける

次に、それぞれの座標(samplesデータ)がどちらの中心(centersデータ)に距離が近いかを計算して、クラスターに分けます。距離を測る計算式はこちら。

(sample_xcenter_x)2+(sample_ycenter_y)2

試しにsamples(1, 1)の、centers(1, 1.1)とcenters(1, 2.1)に対するそれぞれの距離を具体的に計算してみると

(11)2+(11.1)2=0.1

(11)2+(12.1)2=1.1

となります、よってsamples(1.1)はcenters(1, 1.1)に近いと言えます。

他のsamplesとcentersの距離計算は省きますが、この例であればsamples(1, 1)以外の4つのデータはすべてcenters(1, 2.1)の方が近そうです。

よってクラスター分けの結果は次のようになりそうです。

(1, 1.1) (1, 2.1)
(1, 1) (1, 2)
(2, 2)
(4, 5)
(5, 4)

クラスター分けの結果から中心の値を更新する

そして、ここからクラスターの中心(centers)の値を更新します。

centersの更新後の値は、先ほど分けたクラスターごとのサンプルの平均を取ると得られます。

centers(1, 1.1)のクラスターはsamples(1, 1)しか属してないのでそのままですが、centers(1, 2.1)のクラスターに対しては平均を計算して値を更新してあげましょう。

新たなクラスターの中心のx軸の値は 1+2+4+54=124=3

新たなクラスターの中心のy軸の値は 2+2+5+44=134=3.25

この計算を経て、更新した後のcentersは次の値になります。

(1, 1.1),
(3, 3.25)

Notebookに次のコードを入力します。

centers = np.array([
    [1, 1.1],
    [3, 3.25]])
df_ce = pd.DataFrame(centers)
plt.grid()
plt.scatter(df_sa[0], df_sa[1], c='blue')
plt.scatter(df_ce[0], df_ce[1], c='red')
plt.show()

次の図がプロットされます。

5

更新を繰り返すと最終的に中心が安定する

このようにcentersの値を更新する一連の処理を何度か繰り返します。

更新を繰り返すとcentersの値は最終的に(4/3, 5/3), (4.5, 4.5)となり、centersの値が安定し動かなくなります。

centers = np.array([
    [4/3, 5/3],
    [4.5, 4.5]])
df_ce = pd.DataFrame(centers)
plt.grid()
plt.scatter(df_sa[0], df_sa[1], c='blue')
plt.scatter(df_ce[0], df_ce[1], c='red')
plt.show()

プロットした図です。

6

クラスター分けの結果

よって、クラスタリングによるクラスター分けは次の値に落ち着き、

(4/3, 5/3) (4.5, 4.5)
(1, 1) (5, 4)
(1, 2) (4, 5)
(2, 2)

分類の結果は次のようになります。

クラスターA クラスターB
(1, 1) (5, 4)
(1, 2) (4, 5)
(2, 2)

次のコードで、

cluster_a = np.array([
    [1, 1],
    [1, 2],
    [2, 2]])
cluster_b = np.array([
    [5, 4],
    [4, 5]])
df_a = pd.DataFrame(cluster_a)
df_b = pd.DataFrame(cluster_b)
plt.scatter(df_a[0], df_a[1], c='orange')
plt.scatter(df_b[0], df_b[1], c='lightgreen')
plt.grid()
plt.show()

次のようにプロットできます。

7

k平均法のロジックを作ってみよう

それでは、前のセクションでは手計算したk平均法のロジックを、自前で実装してみましょう。

k平均法のロジックを内包するクラスとしてMyKMeansクラスを用意します。MyKMeansクラスの初期化では、次を指定します。

  • 欲しいクラスターの数
  • クラスターの中心を更新する回数
  • 乱数を制御するパラメータ

乱数の制御にはNumPyを使用します。

class MyKMeans(object):
    def __init__(self, n_clusters=8, max_iter=300, random_state=None):
        self.n_clusters = n_clusters
        self.max_iter = max_iter
        self.random_state = random_state
        if self.random_state:
            np.random.seed(self.random_state)

クラスターの中心の初期値を決める

まず、クラスターの中心の初期値を決めましょう。

下記はNumPyのrandom.permutationを利用して、クラスターの中心の座標を初期値サンプルデータからランダムに選び取る処理です。

class MyKMeans(object):
    def __init__(self, n_clusters=8, max_iter=300, random_state=None):
        self.n_clusters = n_clusters
        self.max_iter = max_iter
        self.random_state = random_state
        if self.random_state:
            np.random.seed(self.random_state)

    def fit(self, X):
        initial = np.random.permutation(X.shape[0])[:self.n_clusters]
        self.cluster_centers_ = X[initial]
        return self

より詳細に説明すると、次の処理を記述してます。

  1. サンプルデータのインデックスの数の範囲に収まるランダムな整数を、サンプルデータの数だけ生成する
  2. 生成したランダムな整数の配列をクラスターの数に絞る
  3. クラスターの数に絞られたランダムな整数配列を使って、クラスターの中心の座標の初期値サンプルデータから選び取る

実装した処理が正しいかどうかを、次のコードでテストしましょう。

X = np.array([[1,1],[1,2],[2,2],[4,5],[5,4]])
kmeans = MyKMeans(n_clusters=2, max_iter=5, random_state=1).fit(X)
assert(kmeans.cluster_centers_.shape == (2, 2))
assert(all(c in X for c in kmeans.cluster_centers_))
assert(not np.array_equal(kmeans.cluster_centers_[0], kmeans.cluster_centers_[1]))

このテストコードでは、次のことを確認しています。

  • 生成されたクラスターの中心が2×2の配列になっているか
  • 生成されたクラスターの中心の初期値がサンプルデータの中に含まれているか
  • クラスターの中心の初期値同士は座標がかぶらない

クラスター分けの処理

エンジニアHubに会員登録すると
続きをお読みいただけます(無料)。
登録のメリット
  • すべての過去記事を読める
  • 過去のウェビナー動画を
    視聴できる
  • 企業やエージェントから
    スカウトが届く