For Your ISHIO Blog

データ分析や機械学習やスクラムや組織とか、色々つぶやくブログです。

モデリングのための特徴量の前処理について整理した

久しぶりのデータ分析関連の記事を書きたく、モデリングを行う上での特徴データの前処理について整理しました。本投稿は、下記courseraのKaggleコースの受講とその周辺情報のインプットを整理したものです。より詳細かつ正確な内容については受講してください。

www.coursera.org

目次

前処理の分類

データの前処理には様々な技法が存在するが、自身が利用する「予測モデルのタイプ」×「データ型」により、その処理や特徴量生成方法は大きく異なると認識している。

この文脈における教師あり学習の「予測モデルのタイプ」は、大きく次の2つに大別される。

  1. Tree Based Model
  2. Non Tree Based Model

Tree Based Modelは決定木やランダムフォレスト、AdaBoost等を、Non Tree Based Modelは線形モデルやKNN、Neural Network等を指している。

また、データセット上には、下記のような様々なデータ型が存在する。これらをモデルが識別可能な形式に処理する必要がある。

  1. Numeric(数値データ)
  2. Categorical(カテゴリカルデータ)
  3. Ordinal(順序付きデータ)
  4. Datetime(日付、時間)
  5. Coordinates(座標)

また、本投稿では割愛するが、データセット上には一般的に欠損値が存在する。欠損値もモデルによって処理方法が異なるために注意する必要があるし、そもそもデータセットには、分析者の意図しない/認識しえない範疇で、既に欠損処理された値が埋め込まれている可能性がある。これらを可視化し明らかにし、モデルに合わせてエンコーディングする必要性もある。

以降では、具体的な各データ型における前処理方法について、整理していく。

Numeric(数値)データ

Numericデータにおいて注意すべき1つに、Scaling(スケーリング)がある。Scalingとは、「特徴量の取りうる値の範囲(スケール)を変えること」をさす。例えば、体重と身長、価格と数量では、単位と値の範囲が異なる。特徴量間で極端に異なるスケールのデータセットのままでは、うまく学習できない可能性があり、スケールを揃える必要がある。一般的にTree Based ModelモデルではScalingに依存しないが、Non Tree Based Modelの場合には予測に失敗するか悪影響を及ぼす。

Scalingの影響に関して、Non Tree based Modelに含まれるKNNを利用した例を1つ挙げる。2つの数値データを特徴量としたデータセットにおいて、「オレンジのデータがAとBのどちらと同じグループに含まれるか」のクラス分類を考えてみる。データ間の距離(類似性)をユークリッド距離で計算する場合、左の図ではBとの距離が近くなるために、Bと同じグループに分類される。次に特徴量Xを100倍にしたとする。その結果オレンジのデータはAとの距離が近くなり、Aと同じグループに分類されるだろう。これは相対的に特徴量Xの影響力を肥大化させ、特徴量Yを無視することに繋がる。

f:id:ishitonton:20190223101652p:plain
KNN(Non Tree Based Model)でのクラス分類

逆に、特徴量Xにゼロを掛け合わせたらどうなるか。上記の例ではKNNは特徴量Xを無視することになる。以上はKNNの例だが、線形モデルでもNeural Networkでも勾配降下ベースの最適化に依存するモデルについては、適切なScalingがなされていない場合には、予測結果に悪い影響を与える可能性がある。

特徴量のScalingの方法はいくつか存在する。

  1. Min Max Scalar
  2. Standard Scalar
  3. Outliers(外れ値)への対処
  4. rank transformation
  5. log transformation/square transformation
1. Min Max Scalar

最も直感的かつ簡単なScalingの方法。全ての特徴量の値を同じスケールに変換することである。全ての値から最小値を引き、MinとMaxの差で割る。その結果、値は0から1となる。 デメリットは、0から1の範囲に値を収めるため、標準偏差が小さくなり、外れ値の影響が抑制されること。

SklearnのMinMaxScalerでは、次の変換により行われる。

X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))
X_scaled = X_std * (max - min) + min

なお、sklearnのドキュメント内の説明では、This transformation is often used as an alternative to zero mean, unit variance scaling.と記載があり、次に挙げるStandard Scalarの代替手段として利用されるのだろうか。

sklearn.preprocessing.MinMaxScaler — scikit-learn 0.20.2 documentation

2. Standard Scalar

この変換処理では、平均0、分散1の標準化された分布を得ることができる。標準化(standardization)と同義。まず、平均値を引くことで0前後の値とする。次に、値を標準偏差で除算して、結果の分布が平均0、標準偏差1の標準となるようにする変換方法である。代表的なパッケージとしてはsklearn.preprocessing.StandardScalerがある。

ここでも外れ値には要注意。Standard Scalerでは、平均値と標準偏差を計算する際に外れ値が影響を及ぼし、特徴量のrangeが狭まります。特に、各特徴量の外れ値の大きさが異なるため、各特徴量の変換データの広がりは大きく異なる可能性が出てくる。このように、外れ値が存在する場合にバランスの取れたScalingが保証できない

下記記事も参考にしました。

python - Difference between Standard scaler and MinMaxScaler - Stack Overflow

Standard Scaler v Min Max Scaler in Machine Learning - All Things Software, Data Science and Technology

Feature Scalingはなぜ必要? - Qiita

3. Outliers(外れ値)への対処

Min Max ScalarやStandard Scalarにおいても、外れ値の影響について述べたが、特に線形モデルでは、外れ値の有無が予測精度に大きく影響を与える。これらに対処するために、下限と上限を決め、2つの選択された値の間で特徴量を切り取る方法などがとられる。Kaggleコース内では、特徴量の分布の中から1%~99%のデータを切り取る方法が提案されている。

Scipyのwinsorizationモジュールを利用すると、データから切り取ったデータを返してくれる。

scipy.stats.mstats.winsorize — SciPy v0.14.0 Reference Guide

4. rank transformation

特徴量内の各値の間隔が等しくなるように、数値の順序を保って数字を振り直す方法。例えば、外れ値が存在する場合には、外れ値を他の値に近づける効果があるためMin Max Scalerよりも有効な手段となる。チキチキ外れ値の処理をやっている時間がない場合には有益。

ただし、テストデータへの適用時には注意が必要。事前にTrainデータとTestデータを連結してrank transformationを行うか、もしくが特徴量とランク値のmapping情報をストアしておく必要がある。

rank transformationには、scipyにはrankdataモジュールが提供されている。値が等しいときに、どのようにランクを割り当てるか、いくつかオプションが用意されている。

>>> from scipy.stats import rankdata
>>> rankdata([0, 2, 3, 2])
array([ 1. ,  2.5,  4. ,  2.5])
>>> rankdata([0, 2, 3, 2], method='min')
array([ 1.,  2.,  4.,  2.])
>>> rankdata([0, 2, 3, 2], method='max')
array([ 1.,  3.,  4.,  3.])
>>> rankdata([0, 2, 3, 2], method='dense')
array([ 1.,  2.,  3.,  2.])
>>> rankdata([0, 2, 3, 2], method='ordinal')
array([ 1.,  2.,  4.,  3.])

scipy.stats.rankdata — SciPy v0.16.1 Reference Guide

5. log transformation/square root transformation

多くのモデルは、正規分布を仮定していることが多いので、対数変換により正規分布に近似させる。データを正規分布に従わせる以外にも、外れ値が含まれるデータの分散を小さくするためにも使われる。すなわち、対数変換により極端に大きすぎる外れ値を平均値に近づける。これに伴い、ゼロに近い値をもう少し区別しやすくなる。特徴量の平方根を取る方法も同様の効果が見込める可能性がある。Non Tree Based Modelの中でも、特にNeural Networkで有効。

なお、下記のブログでは、場合によっては分散が増大するケースがあることも書かれている。 yolo-kiyoshi.com

Categorical / Ordinalデータ

Categoricalデータとは、統計用語集によると、質的データと同義で、名義尺度や順序尺度のデータをさす。Ordinal(順序付き)データは、Order Categoricalデータとも呼ばれ、Categoricalデータに含まれる。 Categoricalデータは、性別(男性/女性)や出身都道府県(北海道/青森県岩手県...)、チケットのクラス(エコノミークラス/ビジネスクラス/ファーストクラス)などが該当する。ここでチケットのクラスは、一般的に大小には意味があるが、間隔には意味がないデータであり、Ordinalデータに含まれるだろう。すなわち、チケットのクラスがNumericデータである場合、「エコノミー(1)とビジネス(2)」の差異と「ビジネス(2)とファースト(3)」の差異は等しいと解釈される。Ordinalデータでは、どちらの差異が大きいかはわからない。

Categoricalデータの一般的なエンコード方法は下記のとおりです。

  1. Label Encoding
  2. Frequency Encoding
  3. One-Hot Encoding

上記以外にも、最近ではTarget Encodingなどの手法もKaggleではよく利用されている。

1. Label Encoding

Ordinal featureの最もシンプルなエンコード方法であり、この特徴量のユニークな値を異なる数値にマッピングする方法である。Ordinalデータに限らず、Label EncodingはKaggleのCompetitionでのベースモデルとして、とりあえずCategoricalデータを全てLabel EncodingしているKernelをよく目にする。それほど、最も簡単な方法の1つである。Label EncodingはTree Based Modelでうまく作用するが、逆にNon Tree Based Modelでは効果的ではない

Label Encodingのラベル方法には、次の2通りがある。

  1. alphabetical order
  2. sorted order

alphabetical orderは、SklearnのLabel Encoderモジュールのデフォルトである。すなわち、アルファベットや数字の順序に準拠し、ラベルを振り直す。

>>> from sklearn import preprocessing
>>> le = preprocessing.LabelEncoder()
>>> le.fit([1, 2, 2, 6])
LabelEncoder()
>>> le.classes_
array([1, 2, 6])
>>> le.transform([1, 1, 2, 6]) 
array([0, 0, 1, 2]...)

>>> le = preprocessing.LabelEncoder()
>>> le.fit(["paris", "paris", "tokyo", "amsterdam"])
LabelEncoder()
>>> list(le.classes_)
['amsterdam', 'paris', 'tokyo']
>>> le.transform(["tokyo", "tokyo", "paris"]) 
array([2, 2, 1]...)

sorted orderはpandasのfactorizedモジュールで実装される。値が出現する順序でラベル付けを行う。 既にデータが何かしら意味ある方法でソートされている場合に有効となる。

>>> import pandas as pd
>>> labels, uniques = pd.factorize(['b', 'b', 'a', 'c', 'b'])
>>> labels
array([0, 0, 1, 2, 0])
2. Frequency Encoding

カテゴリーが出現する頻度にデータを置き換えるエンコード方法であり、Tree Based Modelで有効。カテゴリーの頻度がTargetデータとの間に相関がある場合には、線形モデルにおいても効果がある。

注意すべき点としては、仮に複数のカテゴリーで同じ頻度を持つ場合、それらの値を持つデータを区別をすることができないため問題になる可能性がある。この場合の対処として、下記サイトにはwill need to change it to ranked frequency encodingと記載されている。当初、ranked frequency encodingとはよくわからなかったが、恐らくFrequency Encodingとは別に、Label Encodingの特徴量も用意すれば、Tree Based Modelであれば分割可能だということだと思う。

### FREQUENCY ENCODING

# size of each category
encoding = titanic.groupby('Embarked').size()
# get frequency of each category
encoding = encoding/len(titanic)
titanic['enc'] = titanic.Embarked.map(encoding)

# if categories have same frequency it can be an issue
# will need to change it to ranked frequency encoding
from scipy.stats import rankdata

4. Feature Preprocessing & Generation — Data Science 0.1 documentation

3. One-Hot Encoding

One Hot Encodingについては、カテゴリー変数をアルゴリズムが学習しやすいように0と1で表現する方法。エンコード方法には、sklearnのOneHotEncoderモジュールやPandasのget_dummiesメソッド等がある。

One-Hot Encodingは既に最大値1、最小値0にスケーリングされており、Non Tree Based Modelに適用する場合に有効。One-Hot Encodingは、カテゴリー数だけバイナリデータが特徴量として生成される。これは1つの特徴量を効率的に分類しようとするTree Based Modelでの分類を困難にする

次のブログで、Tree Based ModelでOne-Hot-Encodingを実行したプロセスの図が描かれているが、直感的に効率が悪いことが確認できると思う。

f:id:ishitonton:20190224173803p:plain
Visiting: Categorical Features and Encoding in Decision Trees

Visiting: Categorical Features and Encoding in Decision Trees

Categoricalデータにユニーク値が多く含まれている場合には注意が必要である。One-hot Encodingによって、ほとんどゼロの値を取る大量の特徴量が生成される。これらのデータを効率的に利用するために、Sparse Matrixが有用。RAM上に、すべてをStoreせずに、Non-Zeroの要素だけを保存し、メモリを節約できる。目安としては、カテゴリー内の全ての値の半分以上がゼロであるときに有用。XGBoost、LightGBM、sklearnなど、一般的なライブラリのほとんどでSparse Matrixは利用可能。

Lasso on dense and sparse data — scikit-learn 0.20.2 documentation

Sparse matrices (scipy.sparse) — SciPy v1.2.1 Reference Guide

Sparse Matrixを用いたLightGBMの例

LightGBM. Baseline Model Using Sparse Matrix | Kaggle

Datatimeデータ

Datetimeデータの特徴量生成方法には、大きく2つの方法がある。

  1. 期間内のある瞬間の情報
  2. 特定のイベントからの経過時間
1. 期間内のある瞬間の情報

ある期間(1カ月/1年)における週、秒、分、時、日といったデータを追加することができる。これらは、データ内の繰り返しのパターンを捉えるのに有効。例えば、毎月20日は給料日である場合、その繰り返しの情報は、支払いや売上の予測に有効かもしれない。

2. 特定のイベントからの経過時間

ある瞬間からの経過時間に関する情報である。その「ある瞬間」とは、固定の場合と変動の場合がある。前者の例でいえば、「2019年1月1日から経過した日数」が特徴量となる。後者の場合、「日曜からの経過日数」や「前回のセールからの経過日数」などレコードに依存する情報となる。

また、複数のdatetimeデータを保持してている場合には、datetimeデータ同士の差分情報を特徴量として生成することも有効な場合がある。

Coordinatesデータ

座標データは、これまで挑戦したことがないので簡易的な記述とさせていただくが、特徴量生成方法には次の2つが一般的。

  1. 距離の計算
  2. 統計データの利用
1. 距離の計算

仮に地図上に何か重要な地点が存在する場合に、その重要地点との距離を計算し特徴量とする方法である。例えば、もし建物の位置に関する追加データがある場合には、そこから最も近い店舗の距離を計算し特徴量として追加できる。 追加データがない場合にも、様々な工夫により、特徴を生成することが可能である。以下に例を挙げる。

  • 対象のエリアをさらにサブエリアに分割(クラスタリングやグリッド線での分割)し、各サブエリア内で最も高価な建物を代表点と定める。そして代表点までの距離を計算し特徴量とする。
  • サブエリアの中心地点からの距離を計算する。
2. 統計データの利用

オブジェクト周辺エリアの統計データを利用することも有効である。例えば、そのエリアの平均家賃の計算など。

おわりに

もし間違っている箇所があればご指摘下さい。長くなってしまったが、欠損値についてもいつか整理したい。