CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: scikit-learn で主成分分析 (PCA) してみる

主成分分析 (PCA) は、主にデータ分析や統計の世界で使われる道具の一つ。 データセットに含まれる次元が多いと、データ分析をするにせよ機械学習をするにせよ分かりにくさが増える。 そんなとき、主成分分析を使えば取り扱う必要のある次元を圧縮 (削減) できる。 ただし、ここでいう圧縮というのは非可逆なもので、いくらか失われる情報は出てくる。 今回は、そんな主成分分析を Python の scikit-learn というライブラリを使って試してみることにした。

今回使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.12.4
BuildVersion:   16E195
$ python --version
Python 3.6.1

下準備

あらかじめ、今回使う Python のパッケージを pip でインストールしておく。

$ pip install matplotlib scipy scikit-learn

主成分分析の考え方

前述した通り、主成分分析はデータセットの次元を圧縮 (削減) するのに用いる。 ただし、実は元のデータセットと分析結果で次元数を変えないようにすることもできる。 それじゃあ圧縮できていないじゃないかという話になるんだけど、実は分析結果では次元ごとの性質が異なっている。 これは、例えるなら「すごく重要な次元・それなりに重要な次元・あんまり重要じゃない次元」と分かれているような感じ。 そして、その中から重要な次元をいくつかピックアップして使えば、次元の数が減るというわけ。 もちろん、そのとき選ばれなかった「あんまり重要じゃない次元」に含まれていた情報は失われてしまう。

では、主成分分析ではどのような基準で次元の重要さを決めるのだろうか。 これは、データの分散が大きな次元ほど、より多くの情報を含んでいると考える。 分散というのは、データのバラつきの大きさを表す統計量なので、ようするに値がバラけている方が価値が大きいと捉える。 分散が小さいというのは、ようするにどの値も似たり寄ったりで差異を見出すのが難しいということ。 それに対し、分散が大きければ値ごとの違いも見つけやすくなる。

例えば、次のような x 次元と y 次元から成る、二次元のデータを考えてみよう。 この中には (1, 2), (2, 4), (3, 6) という三点の要素が含まれる。

f:id:momijiame:20170402110001p:plain

ここで x 次元の標本分散は  \frac{2}{3} で、y 次元の標本分散は  \frac{8}{3} になる。 主成分分析の考え方でいくと y 次元の方が分散が大きいので、より重要といえる。 ただ、上記のデータは二つの次元が相関しているようだ。 相関しているということは、似たような情報を含む次元が二つある、とも捉えることができる。

では、上記で相関に沿って新しい次元を作ってみたら、どうなるだろうか?

f:id:momijiame:20170402121107p:plain

値の間隔はピタゴラスの定理から  \sqrt{5} となることが分かる。 これは x 次元の間隔である 1 や y 次元の間隔である 2 よりも大きい。

f:id:momijiame:20170402121423p:plain

間隔が大きいということは分散も大きくなることが分かる。

続いては、先ほどの相関に沿って作った次元とは直交する軸でさらに新しい次元を作ってみよう。

f:id:momijiame:20170402121737p:plain

今度は、新しい次元からそれぞれの要素を見てみよう。 このとき、全ての要素は同じ場所にいるので間隔は 0 になっている。 つまり、分散も 0 なので、この次元には全然情報が含まれていないことになる。

上記の作業によって、情報がたくさん含まれる次元と、全く含まれない次元に分けることができた。 あとは、最初に作った情報がたくさん含まれる次元だけを使えば、二次元を一次元に圧縮できたことになる。 実は、これこそ正に主成分分析でしている作業を表している。

実際に試してみる

やっていることの概要は分かったので、次は実際にその通りになるのか試してみよう。 データセットとしては、まずは先ほどの三点をそのまま使ってみる。

次のサンプルコードでは、相関した三点のデータを主成分分析している。 そして、元のデータと分析結果を散布図にした。 また、分析結果の各次元の寄与率というものも出力している。 scikit-learn では

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt
from sklearn.decomposition import PCA


def main():
    # y = 2x
    features = np.array([[1, 2], [2, 4], [3, 6]])

    # グラフ描画サイズを設定する
    plt.figure(figsize=(12, 4))

    # 元データをプロットする
    plt.subplot(1, 2, 1)
    plt.scatter(features[:, 0], features[:, 1])
    plt.title('origin')
    plt.xlabel('x')
    plt.ylabel('y')

    # 主成分分析する
    pca = PCA()
    pca.fit(features)

    # 分析結果を元にデータセットを主成分に変換する
    transformed = pca.fit_transform(features)

    # 主成分をプロットする
    plt.subplot(1, 2, 2)
    plt.scatter(transformed[:, 0], transformed[:, 1])
    plt.title('principal component')
    plt.xlabel('pc1')
    plt.ylabel('pc2')

    # 主成分の次元ごとの寄与率を出力する
    print(pca.explained_variance_ratio_)

    # グラフを表示する
    plt.show()


if __name__ == '__main__':
    main()

それでは、上記のサンプルコードを実行してみよう。 出力されたリストは、分析結果の各次元の寄与率を表している。

$ python pca.py 
[ 1.  0.]

寄与率というのは、前述した「各次元の重要度」を表したもの。 その次元に元のデータからどれだけの割合で情報が含まれているかで、全てを足すと 1 になるように作られている。 つまり、主成分分析をした結果から全ての次元を使えば、元のデータセットから情報の損失は起こらない。 ただし、それだと次元も圧縮できないことになる。

先ほどの出力結果を見ると、最初の次元に寄与率が全て集中している。 つまり、最初の次元だけに全ての情報が含まれていることになる。 これは、先ほど主成分分析の概要を図示したときに得られた結論と一致している。

では、上記をグラフでも確認してみよう。 次のグラフは、主成分分析の前後を散布図で比べたもの。 左が元データで、右が分析結果となっている。

f:id:momijiame:20170402123229p:plain

見て分かる通り、先ほど図示した内容と一致している。 ちなみに、主成分分析では分析結果として得られた次元のことを第 n 主成分と呼ぶ。 例えば、最初に作った次元なら第一主成分、次に作った次元なら第二主成分という風になる。 今回の例では第一主成分に必要な情報が全て集中した。

アイリスデータセットを主成分分析してみる

次はもうちょっとだけそれっぽいデータを使ってみることにする。 みんな大好きアイリスデータセットは、あやめという花の特徴量と品種を含んでいる。 この特徴量は四次元なので、別々のグラフに分けたりしないと本来は可視化できない。 今回は、主成分分析を使って二次元に圧縮して可視化してみることにしよう。

次のサンプルコードではアイリスデータセットの次元を主成分分析している。 そして、分析結果から第二主成分までを取り出して散布図に可視化した。 また、同時に寄与率と累積寄与率を出力するようにした。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt
from sklearn.decomposition import PCA
from sklearn import datasets


def main():
    dataset = datasets.load_iris()

    features = dataset.data
    targets = dataset.target

    # 主成分分析する
    pca = PCA(n_components=2)
    pca.fit(features)

    # 分析結果を元にデータセットを主成分に変換する
    transformed = pca.fit_transform(features)

    # 主成分をプロットする
    for label in np.unique(targets):
        plt.scatter(transformed[targets == label, 0],
                    transformed[targets == label, 1])
    plt.title('principal component')
    plt.xlabel('pc1')
    plt.ylabel('pc2')

    # 主成分の寄与率を出力する
    print('各次元の寄与率: {0}'.format(pca.explained_variance_ratio_))
    print('累積寄与率: {0}'.format(sum(pca.explained_variance_ratio_)))

    # グラフを表示する
    plt.show()


if __name__ == '__main__':
    main()

それでは、上記を実行してみよう。 コンソールには寄与率と累積寄与率が表示される。

$ python pcairis.py 
各次元の寄与率: [ 0.92461621  0.05301557]
累積寄与率: 0.9776317750248034

寄与率は先ほど説明した通りで、ここでの累積寄与率は使うことにした次元の寄与率を足したもの。 ようするに、今回の場合なら第一主成分と第二主成分の寄与率を足したものになっている。 累積寄与率は約 0.97 で、ようするに第二主成分までで元のデータの 97% が表現できていることが分かる。

同時に、次のような散布図が表示される。 これは、第一主成分と第二主成分を x 軸と y 軸に取って散布図にしたもの。 点の色の違いは品種を表している。

f:id:momijiame:20170402125421p:plain

本来なら四次元の特徴量で複数の散布図になるところを、主成分分析を使うことで一つの散布図にできた。

まとめ

今回は Python の scikit-learn を使って主成分分析について学んだ。 データセットに含まれる次元が多いと、データ分析なら分かりにくいし、機械学習なら計算量が増えてしまう。 そんなとき主成分分析を使えば、重要さが異なる新たな次元を含んだデータが分析結果として得られる。 その中から、重要なものをいくつかピックアップして使えば、データの損失を最小限に抑えて次元を減らすことができる。

参考文献

実践 機械学習システム

実践 機械学習システム