kaggle で使う特徴量エンジニアリング

Posted on 2019/09/29 in 機械学習 , Updated on: 2019/09/29

特徴量エンジニアリング

LBスコアを向上させるために重要なテクニックである。下記に、新しい特徴量を生成するいくつかのアイデアおよび方法を示す。 新しい特徴量を作成したら、モデルのCVスコアが改善されるかを確認する。これを繰り返して、有用な特徴量を保持し、 そうでない特徴量を削除していく。

一度に多くの新しい特徴量を作成する場合は、forward feature selection , recursive feature elimination , LGBM importance , permutation importance などの方法を用いて、どの特徴量が有用かを判断することができる。

トレイニングデータとテストデータ

ラベルエンコーディングを利用する際には、下記のように train と test を合わせて実行する必要がある。

# train と test の dataframe を縦方向に連結
df = pd.concat([train[columns], test[columns]], axis=0)

## 連結した df に対して、各種エンコーディングを実行

# 処理後の df を train / test に分割
train[columns] = df[:len(train)]
test[columns] = df[len(train):]

また、別々で実行したい場合は、下記のように。

df = train
# 処理を実行

df = test
# 処理を実行

欠損値の処理

欠損値 (np.nan) が含まれるデータに置いて、そのまま LightGBM へ渡すと、各ツリーノードで、欠損値と非欠損値に分割される。 つまり、欠損値は全てのノードに置いて、特別なものという扱いを受けることになり、overfit (過適合) を起こす原因になり得る。

そこで、全ての欠損値を非欠損値よりも低い負の値 (例えば -999) に変換するだけで、LightGBM は他の数字と同じ扱いをすることになり、 オーバープロセスする可能性が低くなる。ただし、これはデータ構造に依存する部分であるため、変換前後で CV を確認し、どちらが最適であるかを確認する必要あり。

# columns は欠損値を含む特徴量
df[columns].fillna(-999, inplace=True)

ラベルエンコード・ファクトライズ(因数分解)・メモリ削減

ラベルエンエンコード (Factorize:因数分解)は、データタイプが"文字列"、"カテゴリ"、"オブジェクト" の特徴量を整数に変換する。 そして、変換後の整数値が、それぞれ128より小さいか、32768より小さいか否かによって、int8 , int16 , int32 へ分別することができる。これによりデータセットが使用するメモリ使用量を削減することができる。

df[columns], _ = df[columns].factorize()

if df[columns].max() < 128:
    df[columns] = df[columns].astype('int8')
elif df[columns].max() < 32768:
    df[columns] = df[columns].astype('int16')
else:
    df[columns] = df[columns].astype('int32')

また、上記以外の特徴量においても、メモリ削減のために kaggle で有名な memory_reduce 機能を利用することができる。 シンプルで安全な方法は、全ての float64float32 に、全ての int64int32 に変換すること。この際、 float16 を使用するのは避ける方がいい。

for column in df.columns:
    if df[column].dtype == 'float64':
        df[column] = df[column].astype('float32')
    if df[column].dtype == 'int64':
        df[column] = df[column].astype('int32')

カテゴリ特徴量

LightGBM でモデルを作成するとき、カテゴリ変数をカテゴリ変数として使用するか、数値として使用するかを選択できる (事前にラベルエンコードの必要あり)。どちらの場合が、CVへ有効かを検証するようにする。 ラベルエンコードを実行した場合は、下記のようにデータタイプを"カテゴリ"として指定するか、intタイプのままにすることで選択できる。

df[column] = df[column].astype('category')

特徴量の分割

一つの特徴量を分割することで、複数の特徴量を作成することができる。例えば、小数点を含む数値の特徴量は、小数点以上と小数点以下の二つの特徴量として分割することができる。

結合・変換・相互作用

複数の特徴量を、一つの特徴量に結合することができる。例えば、first_namefamily_name の特徴量がそれぞれある場合、二つを結合することで full_name という新しい特徴量を作成できる。
もし、first_namefamily_name がターゲットに対して相関を持たない場合、このような新しい特徴量を作成することで、ターゲットと相関を持つ LightGBM に有用な特徴量を作成することができる場合がある。

df['full_name'] = df['first_name'].astype(str) + ' ' + df['family_name'].astype(str)

また、数値特徴量は、加算、除算、乗算などと組み合わせることができる。

# 乗算による新たな特徴量 'x1_x2'
df['x1_x2'] = df['x1'] * df['x2']

頻度エンコード

頻度エンコード(Frequency Encode)は、一つの特徴量のそれぞれの値の出現頻度を LightGBM が認識できるようにする非常に強力なテクニックの一つ。

temp = df['age'].value_counts().to_dict()
df['age_count'] = df['age'].map(temp)

集計・グループ統計

LightGBM にグループ統計を渡すことで、LightGBM は特定のグループの値の出現頻度を判断できるようになる。pandas に次の三つの変数を渡すことで、グループ統計を計算することができる。 - グループ - 対象の変数 - 統計のタイプ

# 特徴量 weight(体重)でグループ化して、特徴量 age(年齢)の mean(平均)を新たな特徴量として作成
temp = df.groupby('weight')['age'].agg(['mean']).rename({'mean':'age_weight_mean'}, axis=1)
df = pd.merge(df, temp, on='weight', how='left')

この特徴量は、各行に、その行の体重グループの平均年齢を追加する。これにより LightGBM は、体重グループの行に、異常な年齢があるかどうかを判断することができる。

標準化 (standardization)

特徴量をそれ自身に対して、標準化する。標準化は、特徴量の平均を0、分散を1にする変換。下記のモデルでの学習時に主に使用する。ロジスティック回帰、SVM、Neural Network などの勾配法を用いるモデルで使用する。

df[column] = (df[column] - df[column].mean()) / df[column].std()

正規化 (normalization)

特徴量をそれ自身に対して、正規化する。

df[column] = (df[column] - df[column].min()) / (df[column].max() - df[column].min())

外れ値の除去・Relax・Smooth・PCA

モデルを混乱させるような、異常値は慎重に削除していく必要がある。例えば、変数の頻度エンコードを適用後、出現頻度が0.1%以下の値などを特定の値 (-9999) に置き換えることで削除することができる。この際、上記の欠損値変換に使用した値と同じ値を使用しないように注意。