LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

【読書会】python機械学習プログラミング_4章

Last updated at Posted at 2016-12-14

データ前処理 よりよいトレーニングセットの構築

  1. データセットにおける欠測値の削除と補完
  2. 機会学習のアルゴリズムに合わせたカテゴリデータの整形
  3. モデルの構築に適した特徴量の選択

1. データセットにおける欠測値の削除と補完

欠測データへの対応(数値データ)

様々な理由により、欠測データがあることはよくある(アンケートで空欄のままの項目など)
ほとんどの計算ツールは欠測値に対応できないため分析前に処理する必要がある

from IPython.display import Image
%matplotlib inline
# Added version check for recent scikit-learn 0.18 checks
from distutils.version import LooseVersion as Version
from sklearn import __version__ as sklearn_version
import pandas as pd
from io import StringIO

#csvを定義
csv_data = '''A,B,C,D
1.0,2.0,3.0,4.0
5.0,6.0,,8.0
10.0,11.0,12.0,'''

# If you are using Python 2.7, you need
# to convert the string to unicode:
# csv_data = unicode(csv_data)

#csvをpandasに取り込む
df = pd.read_csv(StringIO(csv_data))
df

04_01.png

行ごとの欠損値数を数える

#各行の欠損値をカウント
df.isnull().sum()
A    0
B    0
C    1
D    1
dtype: int64

対処1 欠損値を含む行や列を全て消す

最も簡単な処理

#欠損値を含む行を削除
df.dropna()

04_02.png

#欠損値を含む列を削除
df.dropna(axis=1)

04_03.png

他にもいろいろな消し方がある

#全ての列がNaNの場合のみ削除
df.dropna(how='all')

#値が4つ未満しか入っていない行を削除
df.dropna(thresh=4)

#Cの列にNaNが含まれている場合その行を削除
df.dropna(subset=['C'])

対処2 欠損値を補完する

サンプルを削除する方法は便利に思えるが、削除しすぎると情報量の低下につながる
そのため、補完する方法を考える、補間は欠測値の対処に最もよく使用される手法の一つ

方法としては、データセットの他のトレーニングサンプルから欠損値を推測して入れる

一般的な補間方の一つに平均値補完がある
これは、単に欠損値を列全体の平均値で置き換えるだけである

from sklearn.preprocessing import Imputer

#strategy='mean' 平均で補完しますよ
imr = Imputer(missing_values='NaN', strategy='mean', axis=0)
imr = imr.fit(df)
imputed_data = imr.transform(df.values)
imputed_data
array([[  1. ,   2. ,   3. ,   4. ],
       [  5. ,   6. ,   7.5,   8. ],
       [ 10. ,  11. ,  12. ,   6. ]])

NaNを平均値で置き換えている
strategy引数には平均値、中央値、最頻値などが使用できる

Imputerクラスはscikit-learnのいわゆる変換器クラスに属している
変換器クラスはデータの変換に利用される、
変換器には、基本的なメソッドとしてfitとtransformの二つがある
fit:トレーニングセットからパラメータを学習
transform:学習したパラメータに基づいてのデータを変換するために使用
なので流れとしてはまずfit してその後 transformする
※なのでfitするデータとtransformするデータの特徴量は同じでないといけない

順序特徴量の処理

次はカテゴリデータを取り扱う
カテゴリデータには二種類ある
名義特徴量:例)赤、青、黄
順序特徴量:例)XL/L/M/S

まずサンプルデータを作成する

import pandas as pd

df = pd.DataFrame([['green', 'M', 10.1, 'class1'],
                   ['red', 'L', 13.5, 'class2'],
                   ['blue', 'XL', 15.3, 'class1']])

df.columns = ['color', 'size', 'price', 'classlabel']
df

04_06.png

作成したDataFrameには名義特徴量、順序特徴量、数値特徴量の列が含まれている

順序特徴量を正しく解釈させるには、カテゴリの値を数値に変換する必要がある
整数への変換は、自動的に行われる関数はないため明示的に定義しなければならない

size_mapping = {'XL': 3,
                'L': 2,
                'M': 1}

df['size'] = df['size'].map(size_mapping)
df

04_07.png

機会学習ライブラリの多くはクラスラベルが整数値である必要がある
そのためクラスラベルに対して0から順に番号づけを行う

import numpy as np

class_mapping = {label: idx for idx, label in enumerate(np.unique(df['classlabel']))}
class_mapping
{'class1': 0, 'class2': 1}

次に、マッピングディクショナリを使ってクラスラベルを整数に変換できる

df['classlabel'] = df['classlabel'].map(class_mapping)
df

04_08.png

それとは別に、scikit-learnでLabelEncoderというクラスを使用する方法もある

from sklearn.preprocessing import LabelEncoder

#ラベルエンコーダのインスタンス生成
class_le = LabelEncoder()
#クラスラベルから整数に変換
y = class_le.fit_transform(df['classlabel'].values)
y
array([0, 1, 0])

整数になったクラスラベルを元に戻すにはinverse_transformメソッドがある

class_le.inverse_transform(y)
array(['class1', 'class2', 'class1'], dtype=object)

名義特徴量の処理

順序特徴量と同じように行うと、カテゴリーデータの処理で最もよくある間違いの一つを犯すことになる
blue:0
green:1
red:2

これではred>green>blueという設定と同義となってしまう
この問題を回避するには、one-hotエンコーディングという手法を使用する
これは名義特徴量の一意な値ごとにダミー特徴量を作成する

つまり、color特徴量からblue特徴量、green特徴量、red特徴量に変換する
この変換には、scikit-learn.preprocessingモジュールのOneHotEncoderクラスを使用できる

from sklearn.preprocessing import OneHotEncoder

ohe = OneHotEncoder(categorical_features=[0])
ohe.fit_transform(X).toarray()
array([[  0. ,   1. ,   0. ,   1. ,  10.1],
       [  0. ,   0. ,   1. ,   2. ,  13.5],
       [  1. ,   0. ,   0. ,   3. ,  15.3]])

pandasで実装されているget_dummies関数はさらに便利である
get_dummies関数をdfに適用すると文字列値を持つ列だけが変換され、それ以外の列はそのままになる

pd.get_dummies(df[['price', 'color', 'size']])

04_09.png

データセットをとトレーニングセットとテストセットに分割する

まずサンプルを作成する。
今回はUCIのmachine-learning-databasesからwineのデータセットを取得する

#データセットを読み込む
df_wine = pd.read_csv('https://archive.ics.uci.edu/'
                      'ml/machine-learning-databases/wine/wine.data',
                      header=None)

#列名を設定
df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash',
                   'Alcalinity of ash', 'Magnesium', 'Total phenols',
                   'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins',
                   'Color intensity', 'Hue', 'OD280/OD315 of diluted wines',
                   'Proline']

#クロスラベル表示
print('Class labels', np.unique(df_wine['Class label']))

#最初の5行を表示
df_wine.head()

04_10.png

これらのサンプルは、クラス1,2,3のいずれかに属している
これら3つのクラスは、ブドウの種類を示している

このデータセットをテストデータセットとトレーニングデータセットにランダムに分割するにはtrain_test_split関数が便利である。

if Version(sklearn_version) < '0.18':
    from sklearn.cross_validation import train_test_split
else:
    from sklearn.model_selection import train_test_split

#特徴量とクラスラベルを別々に抽出
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values

#トレーニングデータ(70%)とテストデータ(30%)に分割
X_train, X_test, y_train, y_test = \
    train_test_split(X, y, test_size=0.3, random_state=0)

特徴量の尺度を揃える

特徴量のスケーリングはきわめて重要なステップである
勾配降下法といった機会学習と最適化のアルゴリズムの大半は、特徴量の尺度が同じである場合にはるかにうまく動作する

特徴量の尺度を揃える一般的な手法として正規化、標準化の二つがある
正規化:特徴量を0~1の範囲にスケーリングする事
データを正規化するためには、各特徴量の列にmin-maxスケーリングを適用すればよい

X_{norm}^i = \frac{x^i-x_{min}}{x_{max}-x_{min}} \tag{4.4.1}

min-maxスケーリングはscikit-learnで実装されている

from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
X_train_norm = mms.fit_transform(X_train)
X_test_norm = mms.transform(X_test)

min-maxスケーリングによる正規化はよく使用される手法であり、有界区間の値が必要である場合は役立つ
しかし、多くの場合には標準化の方が実用的である
標準化:平均0、標準偏差1となるよう変換すること
特徴量の列が正規分布に従うため、重みを学習しやすくなる
さらに、標準化では外れ値に関する有益な情報が維持される

標準化は以下の式であらわされる

X_{std}^i = \frac{x^i-μ_x}{σ_x} \tag{4.4.2}

scikit-learnではやはり標準化のクラスが実装されている

from sklearn.preprocessing import StandardScaler

#標準化のインスタンスを生成(平均=0, 標準偏差=1, 変換)
stdsc = StandardScaler()
X_train_std = stdsc.fit_transform(X_train)
X_test_std = stdsc.transform(X_test)

有益な特徴量の選択

テストデータよりトレーニングデータの方がモデルのパフォーマンスがはるかに良い場合過学習という
過学習」:モデルがトレーニングセットの観測結果にパラメータを適合させすぎていて、現実のデータにうまく汎化されないこと

03_06.png

過学習を減らすための一般的な方法は以下のとおり
- さらに多くのトレーニングデータを集める
- 正則化を通じて複雑さにペナルティを科す
- パラメータの数が少ない、より単純なモデルを選択する
- データの次元の数を減らす

今回は正則化と特徴量の選択による次元削減によって過学習を減らす

バイアスとバリアンスのトレードオフを探る方法の一つとして、正則化に基づいてモデルの複雑さを調整することが挙げられる
正則化は共線性を処理する便利な方法で、データからノイズを除いて過学習を防ぐことができる

L2正則化による解

L1:||w||_2^2 = \sum_{j=1}^{m}w_j^2 \tag{4.5.1}

目的は、トレーニングデータセットのコスト関数を最小化する重み係数の組み合わせを見つけ出す
正則化は重みづけが小さくなるよう促す様なペナルティ項をコスト関数に追加する

L2正則化の二次の項は網掛けの円で表される
ここで重み係数は正則化の「予算」を越えられない
つまり重み係数の組み合わせは網掛け部分からはみ出せない

ペナルティの成約下では、ペナルティを受けないコスト関数の等高線とl2の円が交差する点を
選択するのが最善の措置である

L1正則化による疎な解

L2:||w||_1 = \sum_{j=1}^{m}|w_j| \tag{4.5.2}

L1のペナルティは重み係数の絶対値の和である。
L1正規化の等高線は角ばっているため最適化条件は軸上にある可能性が高いため
その条件は疎性を助長する

scikit-learnの正規化モデルは、L1正規化をサポートしている

``python
from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(penalty='l1', C=0.1)
lr.fit(X_train_std, y_train)
print('Training accuracy:', lr.score(X_train_std, y_train))
print('Test accuracy:', lr.score(X_test_std, y_test))
```

Training accuracy: 0.983870967742
Test accuracy: 0.981481481481

正解率はどちらも98%であり過学習には陥っていない事が分かる

切片を表示してみると三つの値を返す

lr.intercept_
array([-0.38382693, -0.15807656, -0.70044843])

重み係数を表示してみる

lr.coef_
array([[ 0.28024476,  0.        ,  0.        , -0.02796835,  0.        ,
         0.        ,  0.71014975,  0.        ,  0.        ,  0.        ,
         0.        ,  0.        ,  1.23612814],
       [-0.64400828, -0.06877713, -0.05720421,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        , -0.92673065,
         0.06017769,  0.        , -0.37101057],
       [ 0.        ,  0.06154617,  0.        ,  0.        ,  0.        ,
         0.        , -0.63574248,  0.        ,  0.        ,  0.49795617,
        -0.35815933, -0.57168028,  0.        ]])

重みが0になっているものが多く、疎であることがわかる
特徴選択の手段であるL1正則化の結果として、このデータセット内の無関係かもしれない特徴量に対しても頑健なモデルがトレーニングされていることが確認できる

逐次特徴選択アルゴリズム

特徴選択による次元削減は、モデルの複雑さを低減し、過学習を回避するもう一つの方法である。
逐次特徴抽出のアルゴリズムは、貪欲探索アルゴリズムの一種である
特徴選択アルゴリズムの目的は二つある
1.問題に最も関連のある特徴量の部分集合を自動的に選択することにより、計算効率を改善すること
2.無関係の特徴量やノイズを取り除く事でモデルの汎化誤差を削減すること

逐次後退選択を使ってみる

逐次後退選択(SBS)は典型的な逐次特徴選択アルゴリズムである
これにより、元々の特徴空間の次元を減らし、分類器の性能低下を最小限に抑えた上で計算効率を改善できる

行う事としては
あらかじめ残しておく特徴量を決めておき、その数に向けて不必要な特徴を削除していく
その際、削除する特徴は、各段階で性能の低下が最も少ないものを選択する(消しても問題ないもの)
具体的には、評価関数Jを用意してそれが最大となる特徴を削除する

アルゴリズムは4つのステップで行われる
1.アルゴリズムをk=dで初期化する。dは全体の特徴空間X_dの次元数を表す
2.Jの評価を最大化する特徴量x^-を決定する。
3.特徴量の集合から特徴量x^-を削除する
4.kが目的とする特徴量の個数に等しくなれば終了する。そうでなければステップ2に戻る

残念ながらscikit-learnには実装されていない
(pythonのコードは教科書に載っています)

ランダムフォレストで特徴量の重要度にアクセスする

データセットから重要な特徴量を選択するもう一つの便利な方法は、ランダムフォレストを使用することである
ランダムフォレストを使用すれば、データが線形分離可能かどうかについて前提を設けなくても、フォレスト内の全ての決定木から計算された不純度の平均的な減少量として特徴量の重要度を測定できる

都合よく、sikit-learnのランダムフォレストは特徴量の重要度を計算してくれる

from sklearn.ensemble import RandomForestClassifier

feat_labels = df_wine.columns[1:]

forest = RandomForestClassifier(n_estimators=10000,
                                random_state=0,
                                n_jobs=-1)

forest.fit(X_train, y_train)
importances = forest.feature_importances_

indices = np.argsort(importances)[::-1]

for f in range(X_train.shape[1]):
    print("%2d) %-*s %f" % (f + 1, 30, 
                            feat_labels[indices[f]], 
                            importances[indices[f]]))

plt.title('Feature Importances')
plt.bar(range(X_train.shape[1]), 
        importances[indices],
        color='lightblue', 
        align='center')

plt.xticks(range(X_train.shape[1]), 
           feat_labels[indices], rotation=90)
plt.xlim([-1, X_train.shape[1]])
plt.tight_layout()
#plt.savefig('./random_forest.png', dpi=300)
plt.show()

04_11.png

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up