データ分析がしたい

企業でデータ分析などやっています。主にRやPythonによるデータマイニング・機械学習関連の話題やその他備忘録について書いてます。

scikit-learnを用いたサンプルデータ生成

機械学習の勉強や新しいアルゴリズムのテストをする場合、irisなどのシステム組み込みのサンプルデータを利用するか、UCIリポジトリなどのネット上の公開データから良さげなものを探すというのが一般的だと思います。

しかしながら、irisなどの組み込みデータは一般にデータ数が少なく、分類問題として物足りなかったり、ネット上の公開データを利用するにしても適当なデータ数や特徴量数、問題設定や難度のデータを探すのが難しいですし、前処理が必要なデータも多く手軽に使えるサンプルデータとなると中々見つけられないといったことがあるかと思います。

そういった場合、適当なデータ数や難しさのデータを自分で生成して利用すると、後の計算コスト評価や機械学習アルゴリズムの理解において色々と便利です。
サンプルデータの作り方としては、何らかの統計モデルに基づいて作る方法もありますが、データの質にこだわらないのであればscikit-learnのサンプルジェネレータを使って作るのが手っ取り早いです。

本記事では特にscikit-learnのmake_classificationを利用した分類問題用データの作成方法について紹介しますが、scikit-learnには分類問題以外にも回帰やマルチラベル分類用のデータや疎なデータを生成する関数もあるので、興味があれば調べてみると良いと思います。(http://scikit-learn.org/stable/index.html

make_classificationの使い方

make_classification関数はsklearn.datasetsに含まれており、サンプル数や特徴量数などいくつかのパラメータを指定するだけで、簡単に分類問題用のサンプルデータ作成することができます。
関数定義は下記の通りです。

sklearn.datasets.make_classification(
    n_samples=100,n_features=20, n_informative=2, n_redundant=2,
    n_repeated=0, n_classes=2, n_clusters_per_class=2, weights=None,
    flip_y=0.01, class_sep=1.0, hypercube=True, shift=0.0, scale=1.0,
    shuffle=True, random_state=None)

パラメータは色々ありますが、例えば、データ数が1000で特徴量が20の2値分類データを作る場合、n_sample=1000、n_feature=20、n_class=2とすると(1000, 20)と(1000,)のnummpy.arrayオブジェクトを返します。そして、n_informative、n_redundant、n_clusters_per_class、flip_yなどでデータ構造や分類の難しさをコントロールする感じです。

下記に各パラメータの大まかな説明を記載しています。

パラメータ名 説明
n_samples 生成するサンプルの数。
n_features 生成する特徴量の数。
n_informative 目的変数のラベルと相関が強い特徴量(Informative fearture)の数。
n_redundant Informative featureの線形結合から作られる特徴量(Redundant fearture)の数。
n_repeated Infomative、Redundant featureのコピーからなる特長量の数(Repeated feature)。
n_classes 分類するクラス数。2なら2値分類問題、3以上なら多値分類問題のデータが作られる。
n_clusters_per_class 1クラスあたりのクラスタ数。後述する生成アルゴリズムを参照。
weights クラスの比率。不均衡データを作りたい場合に指定する。例えば、2値分類問題の場合、Noneとすると0と1が50%ずつだが、[0.9, 0.1] と与えると0が90%、1が10%になる。
flip_y クラスのフリップ率。例えば0.01とすると各クラスの1%の符号がランダムに変更される。
class_sep 生成アルゴリズムに関係するパラメータ。後述する生成アルゴリズムにおける超立法体の頂点の距離。
hypercube 生成アルゴリズムに関係するパラメータ。Trueにすると後述する生成アルゴリズムにおいて、超立方体の頂点にクラスタを配置する。
shift 全ての特徴量にshiftを加算する。Noneが指定された場合、[-class_sep, class_sep]の一様乱数を加算する。
scale 全ての特徴量にscaleを乗算する。Noneが指定された場合、 [1, 100]の一様乱数を乗算する。(scaleはshift後に行われる)
shuffle Trueにすると行と列をシャッフルする。
random_state 乱数を制御するパラメータ。Noneにすると毎回違うデータが生成されが、整数をシードとして渡すと毎回同じデータが生成される。乱数オブジェクトを渡すことも可能。

(参照:sklearn.datasets.make_classification — scikit-learn 0.16.1 documentation


具体的な使い方は次のコードのような感じです。

### classification sample
import numpy as np
from sklearn.datasets import make_classification
from sklearn.cross_validation import train_test_split

from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import roc_auc_score

# サンプルデータの生成
# 1000 samples、5(infomative) + 2(redundant) + 13(independent) =  20 feature のデータを生成
dat = make_classification(n_samples=1000, n_features=20, n_informative=5, n_redundant=2,
                            , n_classes=2, n_clusters_per_class=10)

X = dat[0]
y = dat[1]
print "X shape", X.shape
print "y shape", y.shape

# 学習用とテスト用データの分割
# 80%を学習、20%をテストに利用する
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)

# 学習モデルの構築とパフォーマンス評価
# ロジスティック回帰、ランダムフォレスト、KNNの3つのモデルを作成しそれぞれのAUCを計算
clf = LogisticRegression()
clf.fit(X_train, y_train)
print "LogisticRegression AUC =", roc_auc_score(y_test, clf.predict_proba(X_test)[:,1])

clf = RandomForestClassifier(n_estimators=500, random_state=123)
clf.fit(X_train, y_train)
print "RandomForestClassifier AUC =", roc_auc_score(y_test, clf.predict_proba(X_test)[:,1])

clf = KNeighborsClassifier(n_neighbors=10)
clf.fit(X_train, y_train)
print "KNeighborsClassifier AUC =", roc_auc_score(y_test, clf.predict_proba(X_test)[:,1])

結果

X shape (1000, 20)
y shape (1000,)
LogisticRegression AUC = 0.711838942308
RandomForestClassifier AUC = 0.802534054487
KNeighborsClassifier AUC = 0.793820112179

上記コードではmake_classificationで1000サンプル20特徴量のデータを生成し、train_test_splitで学習とテスト用データに分割し、ロジスティック回帰とランダムフォレスト、KNNの3種類の分類機に入れてAUCを出力しています。(random_stateは毎回同じデータを生成するために指定)

精度の調整に関してはざっくり調べた限り、n_cluster_per_class、flip_yを増やすと分類が難しくなり、class_sepを増やすと簡単になります。また、hyper_cubeをFalseにしたり、informative featureを多めに作って一部の特徴量を削除するといった方法でも分類が難しくなります。分類が簡単すぎてモデル間で差が見えない場合、この辺を調整すると良いと思います。

make_classificationのデータ生成アルゴリズムについて

以下ではmake_classificationで利用されているデータ生成アルゴリズムについて説明しますが、別に知らなくても利用可能なので、興味ある人だけお読みください。

make_classificationで利用されているデータ生成アルゴリズムは下記文献においてMADELONデータの生成に利用されているアルゴリズムのようです。
http://clopinet.com/isabelle/Projects/NIPS2003/Slides/NIPS2003-Datasets.pdf


具体的には下記のような手順でデータを生成しているようです。

  • N(0,1)に従うInformative featureを生成し、クラスタとクラスのラベルに対応づける。その際、各クラスはそれぞれn_cluster_per_class個のラベルに対応するクラスタを持つように構成する。
  • Informative featureからなる行列に-1から1の一様分布からなる要素を持つ行列Aをかけることで、Informative feature間に相関を入れる。
  • n_informative次元で各頂点が±class_sepに位置する超立法体を考え、その頂点に各クラスタを配置する。
  • Informative featureからなる行列に-1から1の一様分布からなる要素を持つ行列Bをかけることで、Redundant featureを生成する。
  • Informative featureとRedundant featureからランダムにRepeated featureを選択する。
  • N(0,1)分布に従うIndependent featureを生成する。
  • N(0,0.1)分布に従うランダムノイズを各特徴量に加算する。(make_classificationでは行ってなさそう)
  • flip_yの比率に応じてランダムにラベルを入れ替える。


簡単に言うと超立方体の頂点にガウス分布に従うデータを作って、それに関係する特徴量や色々なノイズを加えてデータを構成しているイメージです。例えば、n_informativeが2、n_cluster_per_classが2の場合は正方形の頂点に各ラベルに対応したガウス分布データを発生させて色々変換するといった感じです。

データを用いる上での注意点として、構成方法から容易に想像できるように頂点近傍に同種のラベルが集まりやすいアルゴリズムであるため、KNNなどの近傍型アルゴリズムにやや有利なデータが生成されやすいです。(上記のコードでKNNのパフォーマンスが良いのも恐らくこのためと想定されます。)
なので、生成したデータを用いてモデルのパフォーマンスを見るときは、上記のようなデータ構造を持つことを意識して評価した方が良いと思います。