ギークなエンジニアを目指す男

機械学習系の知識を蓄えようとするブログ

単変量解析で見る特徴量エンジニアリング【python】

f:id:taxa_program:20190207003041p:plain:w550

本日は特徴量エンジニアリングの重要性について、scikit-learnでの簡単な実装を交えながら書いてみようと思います。

はじめに

交互作用特徴量や多項式特徴量、単変量非線型変換などで特徴量を追加するのは良いけど、じゃあそこから重要な特徴量を選択するのってどうしたらよいのだろう?と思って勉強してみた記事です。

よく、ランダムフォレストやXgboostなどの木系アルゴリズムで学習させて、feature_importanceを確認するみたいなことを聞いたりしますが、本記事はこれらについては触れていません。

特徴量エンジニアリング

特徴量エンジニアリングとは、機械学習モデルのパフォーマンスを向上させるために、特徴量とも呼ばれる追加の予測因子を構築してデータセットに追加することです。

実世界の問題を解こうとする機械学習実践者の主要な仕事の一つともされています。

特徴量エンジニアリングを行いデータを正しく表現することは、パラメータを適切に選ぶことよりも結果に大きな影響を与えることもあるため、とても重要だと言われています。

単変量統計

単変量統計とは、個々の特徴量とターゲットとの間に統計的に顕著な関係があるかどうかを計算し、最も高い確信度で関連している特徴量を選択しようとする手法です。

この方法の特性としては、単変量であることが挙げられます。つまり、他の特徴量と組み合わさって意味を持つような特徴量は捨てられてしまいます。

単変量テストは計算が高速で、モデルを構築する必要がありません。

今回はこの単変量特徴量選択をscikit-learnを用いて実装し、効果のほどを検証してみます。

検証の概要

scikit-learnで提供されているcancerデータセットのクラス分類に単変量特徴量抽出を適用してみます。検証結果を分かりやすくするために、読込むcancerデータセット以外に、情報のないノイズデータを加えます。単変量特徴量選択が、この情報のない特徴量をいかに検出して、うまく取り除いてくれるかどうかを確認してみます。

検証

cancerデータの読み込み

# データの読み込み
cancer = load_breast_cancer()
pd.DataFrame(data=cancer.data,columns=cancer.feature_names).head()

f:id:taxa_program:20190207000857p:plain
cancerデータセットの一部

# 形状確認
pd.DataFrame(data=cancer.data,columns=cancer.feature_names).shape
# -> (569, 30)

ノイズデータの生成

ノイズデータを生成して、特徴量として加えていきます。

# ノイズデータの生成(50特徴量)
rng = np.random.RandomState(2019)
noise = rng.normal(size=(len(cancer.data), 50)) ## 標準正規乱数 (平均:0.0, 標準偏差:1.0) に従う乱数をsize配列で生成
noise.shape
# -> (569, 50)

# ノイズ特徴量を加える
# 最初の30特徴量はcancerデータセット、続く50特徴量はノイズデータ(合計80)
X_w_noise = np.hstack([cancer.data, noise])
X_w_noise.shape
# -> (569, 80)

もともとのcancerデータの特徴量30 + ノイズデータの特徴量50を追加しています。

単変量特徴量選択

# 訓練用と検証用に分割
X_train, X_test, y_train, y_test = train_test_split(X_w_noise, cancer.target, random_state=0, test_size=0.5)

# SelectPercentileを用いて50%の特徴量を単変量特徴量選択する
select = SelectPercentile(percentile=50)
select.fit(X_train, y_train)
X_train_selected = select.transform(X_train)

# 形状の確認
print('X_train.shape{}'.format(X_train.shape))
print('X_train_selected.shape{}'.format(X_train_selected.shape))
# -> X_train.shape(284, 80)
# -> X_train_selected.shape(284, 40)

特徴量の数が80から40に減っていることが分かると思います。 実際にどの特徴量が選択されたかは、get_supportメソッドを用いることで調べることができます。

# 真偽値のマスクを取得
mask = select.get_support()
print(mask)
# -> [ True  True  True  True  True  True  True  True  True False  True False
# ->   True  True  True  True  True  True False False  True  True  True  True
# ->   True  True  True  True  True  True  True False False False False False
# ->   True False False False False False False False False  True False  True
# ->  False False  True False  True  True False False False  True False False
# ->  False False  True False False False  True False False  True  True False
# ->  False False False False  True False False  True]
# 可視化
plt.matshow(mask.reshape(1, -1), cmap='gray_r')
plt.xlabel('sample index')

f:id:taxa_program:20190207001734p:plain
SelectPercentileで選択された特徴量(黒い部分が選択された特徴量)

可視化したマスクからも分かるように、もとの特徴量のほとんどが選択されており、ノイズの特徴量に関しては、その多くが取り除かれていることが分かると思います。

しかし、もとの特徴量が全て選択されていないことも気になります。

ロジスティック回帰で検証

最後にロジスティック回帰を用いて、全ての特徴量を使った場合と、選択された特徴量のみを使った場合で精度を比較してみます。

from sklearn.linear_model import LogisticRegression

# テストデータの変換
X_test_selected = select.transform(X_test)

# 全ての特徴量
lr = LogisticRegression()
lr.fit(X_train, y_train)
print('Score with all feature: {:.3f}'.format(lr.score(X_test, y_test)))
# ->  Score with all feature: 0.923

# 選択された特徴量のみ
lr = LogisticRegression()
lr.fit(X_train_selected, y_train)
print('Score with only selected feature: {:.3f}'.format(lr.score(X_test_selected, y_test)))
# ->  Score with only selected feature: 0.958

ノイズ特徴量を取り除くと、もとの特徴量(cancerデータセット)のいくつかが失われているにも関わらず、性能が向上していることが分かります。

今回は合成したデータセットサンプルでの結果なので、実際のデータに適応した時に必ずしも性能が向上するとは限りませんが、特徴量が多すぎてモデルを作ることができないような場合や、多くの特徴量がまったく関係ないと思われるように場合には、単変量特徴量選択は有用だと考えられるのではないでしょうか。

以上