「Kaggleで勝つデータ分析の技術」から見る実用的な機械学習の知見
はじめに
機械学習に関する実用的な知見を知るために、「Kaggleで勝つデータ分析の技術」を読んだので、Kaggle以外の場面でも活用できる話題をまとめてみた。本書は機械学習の基本的な内容も含んでいるが、この記事では機会学習に関する一通りの知識を持ち、実問題に利用している読者を想定してまとめた。従って、勾配ブースティング木の仕組みや、回帰タスクの評価方法等、基本的な内容については解説しない。本記事では、評価の落とし穴や、モデルを改善するための特徴量の工夫、チューニングのTipsについて紹介する。
特徴量
Tips 1: 欠損値の扱い
データにはしばしば欠損値が含まれている。欠損値は、そもそも値が存在していない場合の他に、ユーザが意図して入力していない場合や、観測器のエラーによって取得できていない場合等、様々な理由によって生じる。欠損がランダムに発生していない限り、欠損しているという事実が何らかの情報を含んでいると考えられる。また、欠損値を補完することで精度向上に繋がる可能性がある。欠損値の補完方法としては、次の様なものが考えられる。
- 代表値: 学習データ全体やカテゴリ毎の平均値や中央値が利用される。
- 予測値: 欠損値を目的変数とした予測モデルを学習し、欠損値を予測値で補完する。
- 欠損値を用いた特徴量の設計: 欠損の有無を表す二値変数や、欠損変数の個数、欠損変数の組み合わせパターン等が考えられる。
平均値を用いる場合、少数のデータしかないカテゴリでの推定精度が落ちるため、次式で表されるBayesian averageが用いられる。このとき、mは事前知識として得られている推定値で、Cはその推定値を得るために必要となるサンプル数、nはあるカテゴリのデータ数である。この推定値は、カテゴリのデータ数が少ない場合はmに、多い場合にはカテゴリ平均に近づく性質がある。
Tips 2: 数値変数の変換
線形モデル(e.g. 線形回帰、ロジスティック回帰)では、スケールの大きい変数の回帰係数が小さくなり、正則化されにくくなってしまうため、変数間のスケールを揃える必要がある。また、分布の形状を変換することで有益な特徴量とすることができる場合もある。
ただし、いずれの変換についても、学習データと評価データで同一の変換を実施する必要がある。従って、平均値や標準偏差を用いる場合は、学習データで計算した値をいずれのデータの変換に対しても用いる必要がある。
標準化(Standardization)
標準化では、変数の平均を0、標準偏差を1になるように次式の変換を行う。
二値変数では、正例と負例の割合が不均衡の場合、正例か負例のいずれかの変換後の絶対値が大きくなってしまう可能性があるため、標準化が望ましくない場合が多い。
Min-Maxスケーリング
変数の値域を[0, 1]の様な区間に変換する。平均が0にならないことや外れ値の影響を受けるため標準化の方が頻繁に利用されるが、画像データのように元々値域が定まっている場合に利用される。
非線形変換
偏った分布を正規分布に近づけるため等の理由で変数の非線形変換を行う場合がある。具体的には、対数や
Clipping
外れ値を除外するために、下位1パーセンタイル以下や上位99パーセンタイル以上の値を、それぞれ、1パーセンタイル値と99パーセンタイル値に置換する。
順序変数への変換
単純に順位へ変換する他、区間毎にグルーピングするbining、順位を正規分布に近づけるRankGuess等がある。
Tips 3: カテゴリ変数への変換
Label Encoding
Label Encodingではカテゴリ変数の各水準を整数に置換する。通常、各水準に順序が定義されないことが多いため、分岐を繰り返す決定木ベースのモデル以外での利用は適切ではないことが多い。実装は容易なため頻繁に利用されるが、Target Encodingの方が性能面で優れていることが多い。
Target Encoding
目的変数を用いてカテゴリカル変数を数値に変換する。例えば、カテゴリ毎の目的変数の平均値で置換することで、特に時系列性が無く傾向の変化がないタスクにおいて有益な特徴量となる。ただし、当該データを含めた平均値ではリークが発生するため、データを4~10分割程度した上でout-of-foldのデータで平均値を求めて利用する。またfold数を増やしすぎた場合も当該データの目的変数の値の影響が大きくなるため、リークに繋がる恐れがある。従って基本的には、通常の交差検証に加えて、Target Encodingのための分割が必要になる。
目的変数が回帰の場合は全体の平均を、二値分類では正例/負例を1/0として平均値を計算する。多クラス分類では、クラス毎にTarget Encodingによる特徴量を作成する。
Tips 4: 意味の抽出
カテゴリ変数が無意味な記号列ではない場合に、意味に基づいた特徴量を作成する。例えば、部分文字列が製品のカテゴリを意味している製品ID等であれば、有益な特徴量となる。私の経験としても、メタ情報から有益な特徴量を抽出できることが多い。
Tips 5: 時系列データにおける特徴量
ラグ特徴量
ラグ特徴量は、過去のある時点での特徴量である。例えば日付毎の売り上げがある場合は、前日や2日前の売り上げを特徴量に加える。周期性がある場合は、1週間前、1ヶ月前、1年前の値をラグ特徴量として用いる場合もある。また、曜日による変動などが想定される場合は、週平均のラグ特徴量を設計する場合もある。
リード特徴量
リード特徴量は、将来のある時点での特徴量である。通常、目的変数のリード特徴量を加えることはできないが、翌日の天気など、当日の行動に影響を与える特徴量の予測値を特徴量として用いることができる。
その他の話題
- 数値変数、カテゴリ変数を組み合わせた特徴量: 特に勾配ブースティング木では、加減の関係より剰余の関係を捉えにくいため、組み合わせた特徴量が有益な場合がある。
- 次元削減・教師なし学習による特徴量: PCA(Principal Component Analysis, 主成分分析)、SVD(Singular Value Decomposition)、NMF(Non-negative Matrix Factorization)、LDA(Latent Dirichlet Allocation)、LDA(Liner Discriminant Analysis, 線形判別分析)、t-SNE、UMAP、Autoencoder、クラスタリング(K-means, DBSCAN, 凝集型)のような手法がある。
- 匿名化されたデータの変換前の推定値
- データの誤り修正
モデル
Tips 6: モデルの選択
モデル選択の観点には精度や試行錯誤の容易差等がある。次の図に示す選択基準は、特別な要件が無ければ、Kaggleに留まらず一般的に通づる考え方だと思う。
図引用:「Kaggleで勝つデータ分析の技術」
まず初めにGBDT(Gradient Boosting Decision Tree, 勾配ブースティング木)を試し、次にニューラルネットワークや線形モデルを試す。特に線形モデルは、過学習しやすい場合に有効な選択肢となる。図の3段目に記載されている手法は、性能の多様性に貢献し、アンサンブルによる性能向上を目的として追加される。本書では、k近傍方(K-nearest neighbor algorithm, kNN)、ランダムフォレスト(Random Forest, RF)、Extremely Randomized Trees(ERT)、Regularized Greedy Forest(RGF)、Field-aware Factorization Machines(FFM)が紹介されている。
私の経験では、アンサンブル学習を行い実行時や学習時の計算量や保守の容易性を犠牲にしてわずかな性能向上を行う場面は少ないが、オフラインで推論するタスク等では、検討の余地があるかもしれない。
GBDT(Gradient Boosting Decision Tree)
GBDTは予測誤差を予測する決定木で予測値を補完することを繰り返すことで、誤差の最小化を行う。GBDTは、基本的には数値特徴量の大小関係を元に分岐を行うため、欠損値を扱うことができ、スケーリングを行う必要がない。
代表的なライブラリにはXGboost、LightGBM、CatBoostがある。LightGBMはXGboostより高速であるため頻繁に利用される。CatBoostは、前述のTarget Encodingや各深さの条件が等しいOblivious Decision Ttree、少数データに対するアルゴリズムであるOrdered Boostingを採用しているため、相互作用が重要なケースで良い精度が報告されているが、実行速度が低速であるため、広く利用されていない。
ニューラルネットワーク
MLP(Multi Layer Perceptron, 多層ニューラルネットワーク)に代表されるニューラルネットワークでは、(行列演算なので当たり前であるが)欠損値を扱うことができず、特徴量のスケーリングが必要になる。また、ハイパーパラメータの影響を強く受けるため、後述するハイパーパラメータのチューニングが重要となる。ニューラルネットワークは多クラス分類を自然にモデル化できるといった特徴もある。ニューラルネットワークを実装する代表的なライブラリには、TensorFlow/Keras、PyTorch等がある。
ニューラルネットワークの学習に用いられるテクニックは多岐にわたる。本書では、過学習を抑制するために学習時にネットワークを確立的に切断するDropoutや, 学習を安定させるBatch Normalizationが紹介されている。
線形モデル
線形回帰やロジスティック回帰等の線形モデルは、GBDTやニューラルネットワークには性能面で劣るものの、過学習を回避したい場合や、アンサンブルの1つのモデル、後述するスタッキングの最終層で利用される。代表的なライブラリには、scikit-learnやVowpal Wabbitがある。
ニューラルネットワークと同様に、欠損値を扱うことはできず、スケーリングが必要になる。また、GBDT/ニューラルネットワークと異なり、変数間の相互作用や非線形性を考慮したい場合、明示的にモデル化する必要があるため、ドメイン知識がより求められる。また、L1正則化を使った特徴量選択、L2正則化を使った予測に寄与しない特徴量の抑制と組み合わせて利用される[2]。L1正則化とL2正則化を組み合わせたElasticNetも利用される。
モデルの評価
通常、評価を行う際はデータセットを、学習(training)データと評価(test)データに分割し、学習データの一部を検証(validation)[3]データに用いる。分割方法はデータの量や性質、モデルの計算量等に応じて決定されることが多い。一般的な分割方法には以下のものがある。
- Hold-out: 学習、検証、評価に分割する。
- K-fold: 学習データセットをk分割し、k-1を学習に1つを検証に用いる。検証に用いる部分を変更していきk回の検証を行う。K-fold Cross Validation(CV, 交差検証)とも呼ばれる。
- Startified K-fold: fold毎のクラスの割合を可能な限り均等にする。
- Group K-fold: 同一のentity(e.g. ユーザ)に紐づくデータが学習と検証に分かれない様に分割する。
- Leave-One-Out(LOO): 極端にデータが少ない場合に用いられる分割で、k-foldのkをデータ数とする。
Tips 7: リーク/リーケージ(leakage)
(データ)リークは、予測時に利用できない情報を学習に用いてしまい、モデルを過大評価してしまう問題である。将来の情報や、評価データの情報等、本来の予測時には利用できない情報を用いて学習を行ってはならないが、時系列データの交差検証や、実装ミス、目的変数の代理変数の混入等により、意図せずリークが発生してしまう可能性がある。
私の経験では、文書のメタデータにある作成日以降に文書が更新されており、意図せず未来の情報が混入しリークが発生していた経験がある。実データでは最終更新日の様なメタデータが存在しない場合もあるため、ルールベースで(e.g. 文字「更新」と年月日を同一文に含む)未来のデータを含んでいる可能性がある文書を学習データから除外して対応していた。
Tips 8: 時系列データの分割
単純にK-foldの分割を行うと、本来得られない将来のデータを学習に用いることになりデータリークが発生する恐れがある。K-foldを実施して問題無い場合を除き、下図の様な分割を行うことが多い。
図引用:「Kaggleで勝つデータ分析の技術」
Tips 9: Adversarial Validation
学習データと評価データの分布が異なっている場合に、予測の難易度が向上する。Adversarial Validationは、評価データらしいデータを検証に用いることで性能向上をは測るデータ分割手法である。具体的には、「学習データと評価データを分類するモデル」を学習し分類可能な場合に、評価データである可能性が高いデータを検証に用いる。学習データと評価データの分類が困難な場合は、データの分布が一致していると考えられる。
Tips 10: Pseudo Labeling
Pseudo Labeling(擬似ラベリング)は目的変数の無いデータに対して予測値を付与し、学習データに加える半教師あり学習手法である。本書の文脈では評価データに限定しているが、一般には、任意のラベルなしデータに対してルールや機械学習で予測ラベルを付与する手法を指している。弱教師あり学習(Weak Supervsiion)や遠距離教師あり学習(Distant Supervision)、自己教師あり学習(Self-Supervised Learning)の様に呼ばれることもある。
チューニング
Tips 11: ハイパーパラメータのベイズ最適化
検証データを用いたハイパーパラメータのチューニングでは、可能な組み合わせを全て評価するグリッドサーチや、各パラメータの組み合わせをランダムに評価するランダムサーチの他に、探索履歴を用いて効率良く探索を行うベイズ最適化がある。ベイズ最適化を実装しているライブラリには、hyperoptやoptunaがある。
自動で探索できる一方で、計算資源は有限であるため、以下の様な項目を指定して探索を行う必要がある。
- 最適化する評価指標
- 探索するハイパーパラメータ、探索範囲、事前分布: 事前分布としては一様分布の他に、10倍毎に探索したい場合には対数が一様分布になる事前分布を選択する場合がある。
- 探索回数の指定
XGBoostにおけるパラメータ探索
「Mastering XGBoost Parameter Tuning: A Complete Guide with Python Codes」から引用されていたハイパーパラメータの探索手順は以下の通りである。
- ハイパーパラメータの初期値とする。
- eta(学習率): 0.1
- max_depth(決定木の深さ): 5
- min_child_weight(分岐条件となる最小葉数): 1
- colsample_bytree(木毎の特徴量のサンプル割合): 0.8
- subsample(木毎のデータのサンプル割合): 0.8
- gamma(分割時の最小の目的変数の改善量): 0
- alpha(L1正則化の重み): 0
- lambda(L2正則化の重み): 1
- max_depth, min_child_weightの最適化
- max_depth: 3~9を2刻み
- min_child_weight: 1~5を1刻み
- gammaの最適化
- gamma: 0.0~0.4
- subsampleとcolsample_bytreeの最適化
- subsample/colsample_bytree: 0.6~1.0を0.1刻み
- alphaの最適化
- alpha: 0.00001, 0.01, 0.1, 1, 100
- etaを減少させる
Tips 12: 特徴量選択
単変量の統計量に基づく方法
相関係数、カイ二乗統計量、相互情報量を計算して値の高い特徴量から選択する。ただし、特徴量変数間の相互作用は考慮されない。
特徴量の重要度に基づく方法
決定木における分岐に関する情報を重要度として用い、重要度の高い特徴量を選択する。Random Forestでは二乗誤差やジニ不純度が、GBDTでは、特徴量の分岐により得た目的変数の減少量であるゲイン等が用いられる。
また、説明可能AIの分野で提案されている特徴量の重要度の計算手法も利用される[4]。例えば、eli5で実装されているpermutation importanceでは、特徴量をランダムにシャッフルした際の性能低下から重要度を算出する。この他にも、目的変数をシャッフルするnull importanceや、これらとは異なる基準でシャッフルを行うboruta等がある。
反復探索
性能の改善が停止するまで、特徴量の追加を行うことで特徴量選択を行う。
Tips 13: 不均衡データ
特に分類タスクにおいて特定のクラスが多い/少ないことにより、クラス間のデータ量が不均衡(imbalance)になる場合がある。不均衡データで学習を行なった場合に、少数のクラスが適切に学習できない場合があるため、何らかの対処が必要になることがある。
不均衡データを解消する主な方法には次の3つがあるが、分類だけでなく、確率値自体が必要である場合には学習後に確率の補正が必要になる。
アンダーサンプリング(Under Sampling)
アンダーサンプリングは、多い方のクラスのデータをサンプリングして減らすことでデータを均衡させる方法である。利用できるデータが減ってしまうため、異なる乱数でアンダーサンプリングしたデータを用いてバギングを行うことが多い。
私の経験では、学習に利用できる計算資源の制約を上回る程のデータがある場合は、実装が容易なアンダーサンプリングを行うことが多い。
オーバーサンプリング
オーバーサンプリングは、少ない方のクラスのデータを人工的に増やす方法である。特徴空間において近傍のデータ点との中間地点にデータを生成するSMOTE(Synthetic Minority Over-sampling TEchnique)の様な手法が一般的である。[5]
自然言語処理(NLP, Natural Language Processing)の分野では、入力の改変で文法や意味が壊れてしまうことが多いため、私の経験では、ルールベースの拡張を行うEDA(Easy Data Augmentation)やLLM(Large Language Model)を用いたデータ拡張などで代用することが多い。しかし分野を問わず、オーバーサンプリングは学習データにノイズを混入し、実装が複雑になるため、利用する頻度は高くない印象がある。不均衡を解消する場合は後述する重み付けで十分な場合が多く、データ拡張による性能向上は限定的であるため、費用対効果が悪いことが多い。
重みづけ
重みづけは、lossを計算する際に、サンプルやクラス毎に重みを設定する方法である。主要な学習ライブラリでは重みづけをサポートしていることが多い。例えば、sklearn.utils.class_weight.compute_class_weightでは、以下の様にクラスの重みを計算している。
n_samples / (n_classes * np.bincount(y))
np.bincountは値の個数をカウントする関数でクラス毎の出現頻度を計算しており、出現頻度に反比例する重みを計算している[6]。
私の経験では、学習に利用できる計算資源の制約を上回る程のデータがある場合を除き、重み付けが最も現実的な選択肢だと思う。例えデータ量が極端に多い場合でもアンダーサンプリングと重み付けを組み合わせる方法が有効である場合もあった。「Stop Using SMOTE to Treat Class Imbalance」では、3つの手法を比較したシュミレーションを行っており、一貫して重みづけが高い性能であったと報告している。
Tips 14: 閾値の最適化
予測確率自体より分類性能のみに焦点を当てている場合、閾値を最適化することで性能向上が期待できる。閾値の最適化には全探索の他に、最適化アルゴリズムを用いる方法がある。最適化アルゴリズムには勾配を用いないNelder-Mead法やPowell法、制約付きの最適化を勾配を用いて行うCOBYLA法(Constrained Optimization By Linear Approximation method)やSLSQP(Sequential Least Squares Programming)がある。これらの非線形最適化関数はSciPyで実装されている。詳しくは「非線形最適化関数」を参照されたい。
アンサンブル
Tips 15: スタッキング(Stacking)
単純に複数モデルの平均を用いる場合や投票を行う等、様々なアンサンブル手法がある。スタッキングは、複数のモデルの予測スコアを特徴量とするモデルを構築することで、効果的な予測を行うアンサブル手法である。
具体的には、学習データのk-fold分割を行い、out-of-holdで学習したモデルで残りのfoldに予測値を付与する。これは、学習データと評価データの予測値を同質にするためであり、学習データ全体で学習したモデルの予測値だと目的変数がリークした予測値となってしまう。異なるモデルで学習に使うfoldを揃えるかは議論があり、どちらの場合もある。
時系列データの様に、学習データと評価データの分布が異なることが想定される場合は、スタッキングにより過学習を引き起こす可能性が高いため、スタッキングではなく単純なモデルの加重平均等が利用される。
おわりに
機械学習関連の書籍を読んだ際は、「~から見る実用的な機械学習の知見」シリーズとして、今後もまとめていきたい。
-
詳しくは、「 数値変数の分布を変える」を参照されたい。 ↩︎
-
正則化の意味については「L1/L2正則化の意味【機械学習】」を参照 ↩︎
-
開発(development)データとも呼ばれる。 ↩︎
-
説明可能AIについては、「【記事更新】私のブックマーク「説明可能AI」(Explainable AI)」が詳しい。 ↩︎
-
SMOTEについては「解説編:オーバーサンプリング手法解説 (SMOTE, ADASYN, Borderline-SMOTE, Safe-level SMOTE)」が詳しい。 ↩︎
-
マルチラベル分類の場合は少し実装が複雑になり、個々のラベル毎に0と1の重みを計算することになる。「Multi-label classification with class weights in Keras」が参考なったので参照されたい。 ↩︎
Discussion