け日記

最近はPythonでいろいろやってます

読書メモ: Machine Learning 実践の極意(Part I)

「Machine Learning 実践の極意」を読みましたので、そのPart Iの読書メモです。

Machine Learning実践の極意 機械学習システム構築の勘所をつかむ! impress top gearシリーズ

Machine Learning実践の極意 機械学習システム構築の勘所をつかむ! impress top gearシリーズ

  • 作者: Henrik Brink,Joseph W. Richards,Mark Fetherolf,株式会社クイープ
  • 出版社/メーカー: インプレス
  • 発売日: 2017/11/17
  • メディア: Kindle版
  • この商品を含むブログを見る

Jupyter notebookのサンプルコードは以下のレポジトリにあります。

github.com

この本を読むモチベーション

2つあります。 読む箇所に緩急を付けますので、読書メモとしては乱杭になるかと思います。

  • 機械学習で得られたモデルを改善するプロセスやテクニックを獲得する
    • 第1部でアルゴリズムではなくプロセスごとに章立てされており、第2部ではさらに具体的な問題に適用しながらモデルを洗練させていく過程が記されているので、上の目的を果たしそう
    • どうすればさらに精度が高い答えにたどり着けるのか、についてメモしていきます
  • 機械学習に直接関連する学習からここ数ヶ月遠ざかっていたので、復習して、再定着を図る
    • この本はscikit-learn、pandasなどPythonのオーソドックスなツールを使って実装されており、上の目的を果たしそう
    • 「忘れてた」「こういう便利なものもあるのか」という再発見をメモしていきます

Part I 機械学習ワークフローの基礎

第2章 現実世界のデータ

  • 特徴量(説明変数)をどうやって選択すべきか
    • レンジを広くすると無関係な特徴量を含んでノイズとなるが、狭くすると関係する特徴量が抜け落ちるというトレードオフに悩まされる
    • 現実的なアプローチは、最初は関係する可能性が高い特徴量のみでモデルを構築し、それで十分な予測性能が得られない場合は、その他の特徴量を追加して拡張していく
      • 拡張した特徴量セットは特徴選択アルゴリズムで効果的な説明変数を選び出す
  • 目的変数(ground truth)を含むデータセットをどうやって収集すべきか
    • 既知の目的変数を含むデータセットを使って、未知の目的変数を含むデータセットから、トレーニングセットの候補を抽出する能動学習(active learning)と呼ばれる手法がある
  • トレーニングセットの量はどれくらい必要か(十分なのか)
    • データの典型性が維持されていれば、多ければ多いほうが良い
    • トレーニングセット数を増やしていき、予測性能が頭打ちになる点が無いかを調べる(頭打ちしている点が十分なトレーニングセット数)
  • トレーニングセットの典型性は十分なのか
    • トレーニングセットと、最終的に予測したい将来のデータが類似しているかどうか
    • サンプル選択時のバイアス、特徴量の収集方法の変化、データそのものの特性の変化が影響する
  • データが欠測していることに意味(情報利得)の有無で欠測値の扱いが異なる
    • 意味が有る場合は、MissingやNoneなどのカテゴリ値や、-1などの終端値に置き換える
    • 意味が無い場合は、その列の平均値や中央値、あるいは線形回帰などで補完する

第5章 特徴エンジニアリングの基礎

  • 特徴量選択の方法として、変数増加法(forward selection)と変数減少法(backward elimination)の2つがある
    • 変数増加法は以下プロセスで行う(変数減少法はこの逆で、全ての特徴量を含んだ特徴量セットからスタートして、1つずつ減らしていく)
      1. 空の特徴量セットを作る
      2. 利用可能な特徴量から1つを選択して、特徴量セットに一時的に追加して、交差検証を行う
      3. 利用可能な全ての特徴量に対して、2.を行う
      4. 最も性能が良い特徴量を選択して、特徴量セットに追加する
      5. 全ての特徴量が追加されたか、目標の性能に達するまで、2.〜4.を繰り返す
    • 上記の過程で、性能に影響を与える順番で特徴量がソートされるため、重要な特徴量を見抜くことにも役立つ

scikit-learnで特徴選択

特徴選択については、scikit-learnを使った実装まで掘り下げてみたいと思います。 (ちなみに第5章だけレポジトリにサンプルコードがレポジトリにありません。)

これまでランダムフォレストの重要度(例えばRandomForestClassifer.feature_importances_)やロジスティック回帰の重み(例えばLogisticRegressionClassifier.coef_)などで代用してきましたが、scikit-learnには特徴選択用のクラスも提供されています。 それらをいくつか試していきます。

http://scikit-learn.org/stable/modules/feature_selection.html

はじめにscikit-learn付属の乳がんデータセットをDataFrameにロードしておきます。
数値のみの30次元のデータを持ち、今回の検証では使いやすいデータセットとなっています。

import pandas as pd
from sklearn.datasets import load_breast_cancer


dataset = load_breast_cancer()

X = pd.DataFrame(data=dataset.data, columns=dataset.feature_names)
print('X shape: {}'.format(X.shape))
# X shape: (569, 30)

y = pd.DataFrame(data=dataset.target, columns=['target'])
print('y shape: {}'.format(y.shape))
# y shape: (569, 1)

RFE (recursive feature elimination)による特徴選択

変数減少法に近い実装として、sklern.feature_selection.RFE、および、RFEに交差検証を追加したRFECV(交差検証)があります。

RFEは、変数減少法と同じく、最初に全ての特徴量を使ってモデルを構築します。 そのモデルの中で最も重要度の低いを特徴量を削り、性能を再計測するという処理を、指定数(デフォルトでは1)の特徴量になるまで繰り返します。 重要度の指標にはfeature_importancesやcoefが使われます。

乳がんデートセットに対して、分類器にSVMを使い、REFCV(5分割交差検証)で特徴量を削減する実装は以下のとおりです。

  • ranking_には認識率が高くなる特徴量のランキングが昇順でセットされています
    • つまり値が1となっている特徴量のみを使うと最も高い認識率となり、今回の例では14個だけで十分ということです
    • プロットされたグラフを見てみても、特徴量数14の点で最も高い認識率(0.96)をマークしています
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.model_selection import StratifiedKFold
from sklearn.feature_selection import RFECV


# SVMによる分類
estimator = SVC(kernel='linear')

# 5分割交差検証
cv = StratifiedKFold(5)

# 特徴量削減
rfecv = RFECV(estimator, cv=cv, scoring='accuracy', step=1)

# 学習
rfecv.fit(X, y)

print('Feature ranking: \n{}'.format(rfecv.ranking_))
# Feature ranking: 
# [ 1  8  3 17  1  1  1  1  1 14 12  1  1  9 10  6  5  7 15 13  1  2 11 16  1 1  1  1  1  4]

# 最も高い認識率となる特徴量14個
print('Rank 1 features: \n{}'.format(X.columns[rfecv.ranking_ == 1]))
# Rank 1 features: 
# Index(['mean radius', 'mean smoothness', 'mean compactness', 'mean concavity',
#        'mean concave points', 'mean symmetry', 'texture error',
#        'perimeter error', 'worst radius', 'worst smoothness',
#        'worst compactness', 'worst concavity', 'worst concave points',
#        'worst symmetry'],
#       dtype='object')

# 特徴量数と認識率の変化をプロット
plt.plot(range(1, len(rfecv.grid_scores_) + 1), rfecv.grid_scores_)
plt.show()

f:id:ohke:20171118145833p:plain

SelectPercentileによる特徴選択

ある指標に従って、上位X%の特徴量を選択するのがsklearn.feature_selection.SelectPercentileです。
指標には、カイ二乗検定(chi2)、F検定(f_classif)などが使えます。

類似したクラスとして、上位k個の特徴量を選択するSelectKBestがあります。

カイ二乗値の上位20%の特徴量を選択する実装を以下に示します。

from sklearn.feature_selection import SelectPercentile, chi2

# 20%(6個)の特徴量を選択
transformed_X = SelectPercentile(chi2, percentile=20).fit_transform(X, y)

print('Transformed X shape: {}'.format(transformed_X.shape))
# Transformed X shape: (569, 6)

SelectFromModelによる特徴選択

feature_importanceやcoefを使った特徴量選択の方法として、sklearn.feature_selection.SelectFromModeも提供されています。

SelectFromModelは学習済みのモデル(feature_importanceまたはcoefを持つことが前提)と閾値を生成時に渡し、transformメソッドで閾値以上の重要度の特徴のみを抽出した特徴ベクトルへ変換する、前処理として機能します。
あくまで変換のみに使うため、分類は別のモデルで行うこともできます。

SelectFromModelを使って、ランダムフォレストのfeature_importances_で特徴選択するコードを以下に示します。

  • prefitがTrueの場合は、学習済みの分類器を用意して、SelectFromModelにセットします
    • thresholdで選択する特徴量の閾値を設定してます(今回は平均値以上の特徴量を選択します)
  • transformメソッドで30次元から9次元まで削減されます
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_selection import SelectFromModel
from sklearn.pipeline import Pipeline


# ランダムフォレスト
estimator = RandomForestClassifier(random_state=0)
estimator.fit(X, y)

# 重要度が平均値以上の特徴量を抽出
sfm = SelectFromModel(estimator, threshold='mean', prefit=True)
transformed_X = sfm.transform(X)

# 30次元から9次元まで削減
print('Transformed X shape: {}'.format(transformed_X.shape))
# Transformed X shape: (569, 9)

# estimatorから計算した場合と一致することを確認
importances = estimator.feature_importances_
importances[importances > sum(importances)/len(importances)]
# array([ 0.07046207,  0.08107073,  0.14697489,  0.04568418,  0.12102667,
#         0.1117098 ,  0.10620932,  0.04035243,  0.10317082])