下校時刻

メモ、読んだ本のまとめメモ、考えたことのメモ、その他のメモなどが書いてあるブログです(twitter: @hoture6)

「Python Machine Learning」Ch. 4(前処理について)まとめ

Ch. 4は機械学習の前処理の話です。欠損値の処理、カテゴリカル変数の扱い、正則化などscikit-learnにデータを突っ込む前にやるべきことがいろいろ書いてあります。


欠損値の扱い

データが欠損していることは実際のデータでは当たり前なので、なんとかして処理する必要がある。まずpandas.DataFrameにデータを読み込んだら、

df = pd.read_csv('data.csv')
df.isnull().sum()

としてあげると、列ごとに欠損値の数がわかる。

見つかった欠損値は、削除するか埋めるかする。削除はdf.dropna()で、埋めるのはscikit-learnのImputerでやるとよい。

df.dropna() # 一つでもnanを含む行を削除
df.dropna(axis=1) # 一つでもnanを含む列を削除
df.dropna(how='all') # すべての列がnanの行を削除
df.dropna(subset=['C']) # 'C'列がnanの行を削除
from sklearn.preprocessing import Imputer

imr = Imputer(missing_values='NaN', strategy='main', axis=0) # nanがあったら列の平均値で埋める
imr = imr.fit(df)
data_imputed = imr.transform(df.values)

カテゴリカル変数の扱い

カテゴリカルな変数が含まれていることがあり、機械学習の前にうまく数値に変換してあげる必要がある。カテゴリカル変数にはnominalなものとordinalなものがある。

  • ordinal:ソートできるもの(Tシャツのサイズなど)
  • nominal:ソートできないもの(Tシャツの色など)

ordinalなものは単純に数値に置き換えられる場合がある(例えば、サイズなら’M'は1、'L'は2、'XL'は3など(あんまり良くない気がするが))。そのときは、辞書を作ってmapで変換できる。

また、最終的に分類したいクラスのラベルについても、数値でなければいけないことが多い。これについては、辞書を作って自力で変換してあげてもよいし、scikit-learnのLabelEncoderを使ってもよい。

class_map = {label: idx for idx, label in enumerate(np.unique(df['classlabel']))}
df['classlabel'] = df['classlabel'].map(class_map)
from sklearn.preprocessing import LabelEncoder

class_le = LabelEncoder()
Y = class_le.fit_transform(df['classlabel'].values)

nominalな変数については、one-hot encodingをしてあげると良い。ここで、ordinalな変数のように例えば'blue'は1、'red'は2、'green'は3などとしてしまうと、アルゴリズムは'red'は'blue'と'green'の中間なのだと思って学習してしまうので、注意。pandasのget_dummies関数が便利。

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

トレーニングセットとテストセットへの分割

普通、テストセットは全体の20 - 40%くらいを用いるとよいが、極めて大きなデータセットについては1%や10%でも十分な場合がある。分割は、scikit-learnのtrain_test_splitで行う。

from sklearn.cross_validation import train_test_split

X_tr, X_ts, Y_tr, Y_ts = train_test_split(X, Y, test_size=0.3, random_state=0)

変数のスケーリング

ほとんどのアルゴリズムで、特徴量が同じスケールになっていることが必要である。スケーリングには規格化を用いてもよいが、外れ値の影響を受けやすいので標準化(平均を0、標準偏差を1にスケール)の方が望ましい。scikit-learnのStandardScalerを使う。

from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
X_tr_std = sc.fit_transform(X_tr)
X_ts_std = sc.transform(X_ts)

テストセットのスケールにもトレーニングセットの平均・標準偏差を使っていることに注意。

L1正則化による特徴量の選択

一般に、過学習を避けるためにロジスティック回帰等において重みベクトルの大きさに対してペナルティを付ける場合がある。とくに、L2ノルム

$$||\boldsymbol{w}||_{2} = \sum_{j} {w_{j}}^{2}$$

に対してペナルティをつけることでモデルの複雑性を抑えることができる。これは、ロジスティック回帰において重みベクトルの成分が大きくなることは、境界が急になることに対応するからである。

一方、L1ノルム

$$||\boldsymbol{w}||_{1} = \sum_{j} |w_{j}|$$

に対してペナルティをつけることで、不要な特徴量を減らすことができる。これは、L1ノルムが一定の条件のもとでは、特徴量空間の軸上(=ある特徴量が0)の方が有利だからである。特徴量を減らすことで、次元の呪いを避ける効果がある。

from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(penalty='l1', C=0.1) # or penalty='l2'
lr.fit(X, Y)

ランダムフォレストによる特徴量の評価

ランダムフォレストでは、ある特徴量がどれくらい重要かを評価することができる。この評価は、その特徴量を使うことで、平均的にどのくらいimpurityを減らすことができたか(IGが大きかったか)により行われる。scikit-learnのランダムフォレスト分類器のfeature_importances_でアクセスできる。feature_importances_は規格化されている。

from sklearn.ensemble import RandomForestClassifier

forest = RandomForestClassifier(n_estimators=1000, random_state=0)
forest.fit(X, Y)
importances = forest.feature_importances_

また、feature_importances_の値に対して閾値をもうけることで、特徴量選択をしてくれる。

X_selected = forest.transform(X, threshold=0.15)