iMind Developers Blog

iMind開発者ブログ

scikit-learnのPCAを使う

概要

PCAを使う用事があったのでscikit-learnを利用する。

バージョン情報

  • scikit-learn==0.21.2
  • Python 3.7.3

サンプルデータ

ランダムにユーザーにアイテムを割り振るサンプルデータを生成してみる。

要件は下記。

  • ユーザー数は1万
  • アイテム数は3000
  • 1ユーザーにつき1〜100まで可変数のアイテムを持つ
import numpy as np
import scipy.sparse

# アイテムID一覧
ITEM_COUNT = 3000
item_ids = np.arange(ITEM_COUNT)

# ユーザーのアイテムをランダム生成
USER_COUNT = 10000
mtrx = scipy.sparse.dok_matrix((USER_COUNT, ITEM_COUNT), dtype=np.int8)
for user_id in range(USER_COUNT):
    user_item_count = np.random.randint(100)
    user_items = np.random.choice(item_ids, user_item_count) 
    for item_id in user_items:
        mtrx[user_id, item_id] = 1

mtrx = mtrx.transpose().tocsr()

適当なデータなので何かしら特徴が取れるわけではないけど、これでPCAを実行してみる。

scikit-learnでPCA

PCAの実行。

とりあえず2次元にしてみる。

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca.fit(mtrx)

実行結果

TypeError: PCA does not support sparse input. See TruncatedSVD for a possible alternative.

・・・あれ? SparseVectorだとダメなの?

TruncatedSVDを使えと言われる。PCAと意味合いとしてはだいたい同じらしい。

from sklearn.decomposition import TruncatedSVD
pca = TruncatedSVD(n_components=2)
new_mtrx = pca.fit_transform(mtrx)

from matplotlib import pylab as plt
plt.scatter(x=new_mtrx[:, 0], y=new_mtrx[:, 1], alpha=0.1)

f:id:mwsoft:20190622160330j:plain

ランダムに生成したので実行結果もいかにもランダムな形。

結果の保存

出来上がったモデルとsparseなテストデータ、denseな次元圧縮結果を保存しておく。

import pickle

# 学習データの保存
scipy.sparse.save_npz('train_sparse.npz', scipy.sparse.coo_matrix(mtrx))
# 次元圧縮結果の保存
np.save('comp', new_mtrx)
# モデルの保存
with open('pca_model.pkl', 'wb') as f:
    pickle.dump(pca, f)

保存結果のロード

# 学習データのロード
mtrx = scipy.sparse.load_npz('train_sparse.npz')
# 次元圧縮結果のロード
new_mtrx = np.load('comp.npy')
# モデルのロード
with open('pca_model.pkl', 'rb') as f:
    pca = pickle.load(f)

# ロードしたモデルで次元圧縮
pca.transform(mtrx)

手書き数字の2次元化

ランダムだと何やってるのかわかりづらいので手書き文字の0〜4を2次元にしてみる。

手書き文字のサンプルデータ取得。

import sklearn.datasets

digits, label = sklearn.datasets.load_digits(return_X_y=True)
digits = digits / 255

digitsに64次元(8x8)のデータが入ってるのでそれをPCAして0〜4をplot。

from sklearn.decomposition import TruncatedSVD
pca = TruncatedSVD(n_components=2)
digits2d = pca.fit_transform(digits)

from matplotlib import pylab as plt
for i in range(5):
    target = digits2d[label == i]
    plt.scatter(x=target[:, 0], y=target[:, 1], label=str(i))
plt.legend(bbox_to_anchor=(1.02, 1), loc='upper left')

f:id:mwsoft:20190622160715j:plain

ほどほどにわかれた結果が取れた。4は比較的分類しやすく、2と3や0と1はかなり混ざっている。

改定履歴

Author: Masato Watanabe, Date: 2019-06-22, 記事投稿