ちょっとだけ古めの本だけど、Kaggle 本の中でも評価の高いデータ分析技術本です。 たしかによくまとまってて読みやすいですね。 以下まとめメモメモ。
1. 分析コンペとは
分析コンペって何?
- コンペとは
- 特徴量(変数、説明変数)を入力として
- 目的変数を予測する
- 学習データ(提供されるデータ)には上記の 1 と 2 が含まれ、テストデータには 1 だけが含まれる。
- 学習データのイメージ(入力とその答えがある)
- 123, 135, 74, 12, 301, 634 → 15
- 423, 562, 10, 44, 125, 988 → 31
- 904, 111, 64, 15, 877, 502 → 23
- テストデータのイメージ(入力しかない)
- 335, 218, 66, 40, 226, 999 → ?
- 590, 449, 59, 20, 633, 490 → ?
- 771, 703, 12, 30, 550, 300 → ?
- 学習データのイメージ(入力とその答えがある)
- LB: Leaderboard
- 参加者はコンペ期間中にテストデータを使って予測した値を提出する。
- システム側で テストデータの一部 だけを使ってスコアが計算され、Public Leaderboard として現在の順位が公開される。
- コンペ期間が終了すると、テストデータの残りの部分を使ってスコアが計算され、Private Leaderboard として最終的な順位が発表される。
- Shake up
- Public LB (Leader Board) と Private LB の順位が大きく入れ替わること。
- チームマージ
- 他の参加者にリクエストを出してチームを組むこと。
- コンペの終盤に多くのチームマージが行われることがある。
- チームの人数上限は 5 名。
- マージするチーム同士の合計提出回数は「1日の上限 x コンペ日数」を超えてはいけない。毎日上限まで予測値を Submit していると、チームマージできなくなるので注意。
分析コンペのプラットフォーム
- 主なプラットフォーム
- Kaggle (worldwide)
- 最も有名
- 世界中の企業、省庁、研究機関がコンペを開催
- SIGNATE(日本)
- 日本語
- 日本国内の企業、省庁、研究機関がコンペを開催
- TopCoder(worldwide)
- プログラミングコンテストのプラットフォームだが、分析コンペも開催されている
- Kaggle (worldwide)
- カーネルコンペ
- 通常のコンペ … 予測値を提出する。
- カーネルコンペ … Kernel (Notebook) に記述したコードを提出する。
- Kernel 上で学習と予測の両方を実行するタイプと、予測だけを実行すればよいタイプがある(後者はモデルのバイナリをアップロードできるようになっている)。
分析コンペに参加してから終わるまで
Join Competition
ボタンからコンペに参加する- 規約に同意する
- 1 日の Submit 数やチームメンバー数の上限。
- Private Sharing(チームメンバー意外へのコード共有)はダメ。Kaggle の Discussion での共有は OK。
- 外部データの使用可否はコンペにより異なる。
- データをダウンロードする
- 予測値を作成する
- ダウンロードしたデータを使ってモデルを作る
- テストデータに対する予測値を求める
- 予測値を提出する
- 提出サンプル(主に CSV 形式)と同じ形式の提出用ファイルを作成して Submit。1日の提出数上限に注意。
- Public Leaderboard をチェックする
- テストデータの一部を使ってスコア計算した結果の順位が公開される。
- 最終予測値を選ぶ
- コンペの終了前に、最終評価に使う予測値を 2 つ選んで
Use for Final Score
にチェックを入れる。 - 自分で選ばないと、Public Leaderboard の中でスコアが高いものが選ばれる。
- コンペの終了前に、最終評価に使う予測値を 2 つ選んで
- Private Leaderboard をチェックする
- 分析コンペが終了したら Private LB で最終順位を確認する。
- 多くの場合は終了と同時に発表されるが、発表までに時間がかかるものもある。
分析コンペに参加する意義
- 賞金、称号、ランキング
- データ分析の経験
- データサイエンティストとの繋がり
- 就業機会
上位を目指すためのポイント
- 探索的データ分析 (EDA: Exploratory Data Analysis)
- まず優先すべきはデータの理解
- 可視化手法
- 棒グラフ、箱ひげ図、バイオリンプロット、散布図、折れ線グラフ
- ヒートマップ、ヒストグラム
- Q-Q プロット
- t-SNE、UMAP
- テーブルデータのコンペでは、よいデータを作れたかどうかで順位がきまる
2. タスクと評価指標
分析コンペにおけるタスクの種類
タスクの種類ごとに評価指標がいろいろある。
- 回帰タスク → RMSE、MAE
- 分類タスク
- 二値分類
- ラベル (0 or 1) で予測 → F1-score
- 各クラスの確率で予測 → logless、AUC
- 多クラス分類
- マルチクラス分類(予測値はひとつのラベル) → multi-class logloss
- マルチラベル分類(予測値は1つ以上のラベル) → mean-F1、macro-F1
- 二値分類
- レコメンデーション
- 予測値に順位をつける場合 → MAP@K
- その他のタスク
- 物体検出 (object detection)(矩形領域: bounding box で推定)
- セグメンテーション (segmentation)(ピクセル単位で推定)
評価指標 (evaluation metrics)
評価指標は、学習させたモデルの性能やその予測値の良し悪しを測るための指標。
回帰における評価指標
- RMSE: Root Mean Squared Error(平均平方二乗誤差)
- MAE と比べて 外れ値の影響を受けやすい
- scikit-learn の metrics モジュールの
mean_squared_error
関数を使う
- RMSLE: Root Mean Squared Logarithmic Error
- 目的変数の対数をとった値を新たな目的変数として RMSE を最小化すれば RMSLE を最小化することになる。
- 大きな値の影響が強くなってしまう場合などに用いられる
- scikit-learn の metrics モジュールの
mean_squared_log_error
関数を使う
- MAE: Mean Absolute Error
- 外れ値の影響を低減して 評価するときに最適
- scikit-learn の metrics モジュールの
mean_absolute_error
関数を使う
- 決定係数 (R2)
- 回帰分析の当てはまりの良さを示す(1 に近いほど精度の高い予想ができている)
- 決定係数を最大化するのは、RSME を最小化するのと同じ意味
- scikit-learn の metrics モジュールの
r2_score
関数を使う
二値分類における評価指標(正例か負例かを予測する場合)
- 混同行列 (Confusion Matrix)
- TP: True Positive(真陽性) … 予測値を正例として、その予測が正しいとき
- TN: True Negative(真陰性) … 予測値を負例として、その予測が正しいとき
- FP: False Positive(偽陽性) … 予測値を正例として、その予測が誤りのとき
- FN: False Negative(偽陰性) … 予測値を負例として、その予測が誤りのとき
- 完全な予測を行ったモデルの混同行列では、TP と TN のみに値が入る
- 正答率 (Accuracy) と 誤答率 (Error Rate)
- Accuracy = (TP + TN) / (TP + TN + FP + FN)
- ErrorRate = 1 - Accuracy
- scikit-learn の metrics モジュールの
accuracy_score
関数を使う
- 適合率 (Precision) と 再現率 (Recall)
- Precision(適合率) = TP / (TP + FP)
- 陽性と予測したもののうち、正しく陽性と予測できた割合
- ▼陽性適合率 (PPV: Positive Predict Value) とも
- Recall(再現率) = TP / (TP + FN)
- すべての陽性データのうち、正しく陽性と予測できた割合
- ▼感度 (sensitivity) とも
- 0 からの 1 の値をとり、1 に近いほど良いスコア
- Precision と Recall はトレードオフの関係
- 誤検知を少なくしたいときは Precision を重視するとよい(間違えて陽性と判断すると困るケース)
- 正例の見逃しを避けたいときは Recall を重視するとよい(陽性を見逃しては困るケース:例として病気の検出)
- scikit-learn の metrics モジュールの
precision_score
、recall_score
関数を使う
- Precision(適合率) = TP / (TP + FP)
- F1-score(F値) と Fβ-score
- 二値分類のモデルの精度を測るための評価指標
- F1-score は Precision と Recall の調和平均
- F1-score は実務でもよく使われる
- 二値分類タスクで正しく分類できたか (accuracy) だけではなく、A(陽)と B(陰)のどちらに分類するかが大きく意味を持つケースなどで使う(例えば、病気かどうかの判断など)
- F1 = 2TP / (2TP + FP + FN)
- Fβ-score は F1-score から Recall と Precision のバランスを係数βで調整したもの
- Fβ = (1 + β2)・Recall・Precision / (Recall + β2Presision)
- scikit-learn の metrics モジュールの
f1_score
、fbeta_score
関数を使う
- MCC: Matthews Correlation Coefficient
- 不均衡なデータに対するモデルの性能を評価するのに使える
- -1 から +1 の値を取り、+1 は完璧な予測、0 はランダムな予測、-1 は完全に反対の予測を示す
- F1-score は正例と負例の数が反転すると値が変わってしまうが、MCC は同じ値になる
二値分類における評価指標(正例である確率を予測する場合)
- logloss (cross entropy)
- 分類タスクでの代表的な評価指標
- logloss = -Σ(yi log pi + (1-yi)log(1-pi)) / N
- logloss は低い方がよい
- scikit-learn の metrics モジュールの
log_loss
関数を使う
- AUC: Area Under the ROC Curve
- ROC: Receiver Operating Characteristic Curve が描く曲線をもとに計算する(ROC 曲線の下部の面積)
- scikit-learn の metrics モジュールの
roc_auc_score
多クラス分類における評価指標
- multi-class accuracy
- scikit-learn の metrics モジュールの
accuracy_score
関数を使う
- scikit-learn の metrics モジュールの
- multi-class logloss
- scikit-learn の metrics モジュールの
log_loss
関数を使う(二値分類のときと引数の配列の形を変える)
- scikit-learn の metrics モジュールの
- mean-F1、macro-F1、micro-F1
- F1-score を多クラス分類に拡張したもので、マルチラベル分類で用いられる
- quadratic weighted kappa
- マルチクラス分類でクラス間に順序関係がある場合(例: 映画の 1~5 のレーティング)
レコメンデーションにおける評価指標
- MAP@K: Mean Average Precision @K
- 各レコードが 1 つまたは複数のクラスに属しているときに、属している可能性が高いと予測する順に K 個のクラスを予測値とする
評価指標と目的関数
- Objective function(目的関数)
- モデルの学習において最適化される関数。
- 学習では目的関数の値を最小化するように係数などを更新していく。
- 目的関数は 微分可能でなければいけない。
- 主な目的関数
- 回帰タスク … RMSE
- 分類タスク … logloss
- 評価指標と目的関数が一致していない場合、そのモデルは評価指標に対して最適化されていない可能性がある。
評価指標の最適化
評価指標の最適化の例
- 多クラス分類の blanced accuracy における最適化
- 1 割しかないクラスを正しく予測することは、2 割あるクラスを正しく予測することの 2 倍の価値があるので、「確率 × クラスの割合の逆数」が最大となるクラスに分類するのがよい。
- mean-F1 における閾値の最適化
- mean-F1 … レコードごとの F1 スコアの平均
- レコード(ここではオーダー)ごとに閾値を変えないと評価指標を最適できない。
- quadratic weighted kappa における閾値の最適化
- 1〜8 のレーティングを付けるマルチクラス分類では、クラスに順序関係があるので回帰として解くことも分離として解くこともできる。
- quadratic weighted kappa という評価指標では、連続値で予測値を出力したあとにクラス間の閾値を最適化(後処理) するアプローチが効果的。
- 回帰モデルや分類モデルで各クラスの確率の加重平均による予測値を出力する(各クラスの値を i、確率を pi としたときに、Σipi を予測値とする)
- どの値を境にクラス分けをするかの区切り位置を最適化する
- ようするに、モデルによって求めた予測値を単純に四捨五入してクラス分けするのではなく、後処理で閾値を決める。
- イメージとしては、このように等間隔に区切るのではなく、
それぞれうまく分類できる区切り位置を見つける。 scipy.optimize
のminimize
関数で Nelder-Mead 法などを用いる。
- カスタム目的関数による MAE の最適化
- MAE: Mean Absolute Error(平均絶対誤差)は、勾配が不連続で二階微分値が微分可能な点で 0 になってしまうため、xgboost などで目的関数としてセットできない(学習できない)。
- そこで、MAE のグラフ形状に近く、かつ勾配が連続になる代替の関数として、Fair や Psuedo-Huber などの関数をカスタム目的関数としてい使用する。
- MCC の PR-AUC による近似
- MCC は閾値の最適化が必要で、閾値にセンシティブなので、モデル選択の指標としては不安定。
- Kaggle の「Bosch Production Line Performance」では、正例に比べて負例のデータ数が非常に多く、AUC も扱いづらかった。
- 正例が非常に多い場合、MCC の式は Precision と Recall の幾何平均 √(precision × recall) で近似することができる。長丁場のコンペではこういった工夫をして、大局的に良い方向にモデル選択を行うのが重要。
- シンプルにモデル選択のための評価指標を logloss として進めるのも有力(それぞれの予測確率の改善がスコア上昇につながるので)。
リーク (data leakage)
2 種類のリークがある。
- 分析コンペの設計の問題
- 予測に有用な情報が漏れて使えるようになってしまうもの。
- 分析という観点では本質的ではないが、リークがあるときは積極的に見つけざるを得ない。
- 例
- テストデータが学習データに入っている
- テストデータの特徴量に予測対象の値が入っている
- 将来の情報が過去のデータに入っている
- 第三者のデータに有用な情報が含まれている
- etc.
- 実際にあった例
- Google Analytics 関連の予測コンペで、テストデータが Google Analytics のデモアカウントに含まれていた。
- 各種特徴量を比較すると、テストデータと同じユーザーが学習データに含まれていることを判別できた。
- ID に時間情報として扱えるデータが含まれていた。
- モデル作成時の技術的な問題
- バリデーションの枠組みを誤ってバリデーションスコアが不当に高くなってしまうもの。
- これは、参加者が特徴量の作成やバリデーションを行う際に注意するもの。
- 例えば時系列データを扱う場合に、同時刻の似たレコードの目的変数をほぼそのまま予測値としてしまうと、バリデーションでだけ高いスコアが出る。
3. 特徴量の作成
GBDT (Gradient Boosting Decision Tree)
- この種のライブラリとしては
xgboost
、lightgbm
が有名。 - 決定木をベースとしたモデルで、分岐の繰り返しによって変数間の相互作用を反映するため、非線形な関係性も表現できる。
- 数値の絶対値的な大き差ではなく、大小関係のみを考慮するため、データのスケーリングは不要。
- 欠損値をそのまま扱える。one-hot エンコーディングではなく、label エンコーディングを使っても、分岐の繰り返しによって各カテゴリの影響を反映してくれる(特徴量を無闇に増やさなくて済む)。
- 一方、ニューラルネットの場合
- 数値の大きさが影響する。
- 欠損値は何らかの数値に置き換えが必要。
- 前の層の出力を結合する計算によって、変数間の相互作用を反映する。
- → GBDT の主なライブラリ
決定木の気持ちになって考える
- モデルから読み取りづらい情報を追加で与えてやる。
- 例: 別テーブルと結合してデータを関連づける。
- 例: 平均購入価格が重要そうであれば、購入額と購入数から新しい特徴量として作ってやる。
欠損値の扱い
- GBDT 系のライブラリであれば、欠損値はそのまま使うのが基本だが、明示的に補完した方が精度が向上することもある。
- 欠損値をもとに、新たな特徴量を作成するも有効。
- 欠損値のあるカラムを除外するのは得策ではない。
- scikit-learn のランダムフォレストなどでは欠損値をそのまま扱えないが、欠損値として通常取り得ない値(-9999など)を代入することで分岐処理をうまく動かせることがある。
- 欠損値を代表値で埋める方法
- 平均値、中央値、対数変換してから平均をとる。
- 別のカテゴリ変数でグループ分けしてから平均をとる。その際に、グループ内のデータ数が少なくなってしまう場合は、Bayesian Average という平均値計算を使うとよい。
- 欠損値を他の変数から予測する
- 欠損値を補完したい変数を目的変数としたモデルを作って学習する → 予測値で補完する。この際の学習には、テストデータを使ってよい。
- 欠損値から新たな特徴量を作る
- 欠損しているかどうかを示す二値変数
- レコードごとの欠損数
- 欠損の組み合わせによりパターン分類する
- pandas で欠損値の表現を指定して読み込む方法変数ごとに欠損値の表現が異なる場合
# na_values で指定したものをすべて np.nan 化 train = pd.read_csv("train.csv", na_values=["", "NA", -1, 9999])
# 例: col1 列だけ欠損値が -1 で表現されているとき data["col1"] = data["col1"].replace(-1, np.nan)
数値変数の変換
以下の変換は、線形回帰やロジスティック回帰などの線形モデルや、ニューラルネットで有効。 GBDT などの決定木ベースのモデルでは効果がないことに注意。
- 標準化 (standardization)
- 平均を 0、標準偏差を 1 にする。
sklearn.preprocessing.StandardScaler
を使う。StandardScaler
のフィット時には、テストデータを合わせて (pd.concat
) 使ってもよいが、そこまで大きな差異は生じない。
- Min-Max スケーリング
- 変数のとる範囲を特定の区間(通常は0〜1)に押し込める。
sklearn.preprocessing.MinMaxScaler
を使える。- 平均が 0 にならない、外れ値の影響を受けやすいなどのデメリットあり。
- 画素値(0〜255)などには適用しやすい。
- 非線形変換
- 対数をとる (
x2 = np.log(x)
)。 - 1 を加えて対数をとる (
x2 = np.log1p(x)
)。0 が値として含まれているときはこっちを使う。 - 絶対値の対数をとってから符号を戻す。
x2 = np.sign(x) * np.og(np.abs(x))
- Box-Cox 変換、Yeo-Johnson 変換
- 正規分布に近づくように変換してくれる。
- その他
- generalized log transformation
- 絶対値/平方根/n乗する/四捨五入
- 正かどうかで二値化/100円未満の端数を抽出
- 対数をとる (
- clipping
- 外れ値を1%タイルや99%タイルにクリップ
- NumPy の
clip()
関数を使う。
- binning
- 区間ごとにグループ分けする。
- pandas の
cut()
関数、NumPy のdigitize()
関数を使う。
- 順位への変換
- pandas の
rank()
関数を使う方法、NumPy のargsort()
を 2 回適用する方法がある。 - さらにレコード数で割ることで 0〜1 の範囲に収められる。
- pandas の
- RankGauss
- ランク化して正規分布にする。
- ニューラルネットでは通常の正規化よりよい性能を示す。
カテゴリ変数の変換
「水準」という用語は、カテゴリ変数におけるカテゴリのことを示す。
- one-hot エンコーディング
- 各水準かどうかを示す 0, 1 の二値変数を作る(「ダミー変数」と呼ぶ)。
- pandas の
get_dummies()
関数を使う。 sklearn.preprocessing.OneHotEncoder
もある。- カテゴリ変数の水準が多すぎて、ダミー変数が増えすぎてしまう場合は、
- 別のエンコーディング手法を使う。
- グルーピングして水準の数を減らす。
- 頻度の少ないカテゴリを「その他」にまとめる。
- label/oridinal エンコーディング
- 各水準を整数のインデックス(例: 0、1、2)に置き換える。
- 変換後のインデックスは数値の大小に意味がないので、決定木をベースにした手法(GBDT など)でのみ有効。
sklearn.preprocessing.LabelEncoder
を使う。
- feature hashing
- one-hot エンコーディングと同様の考え方だが、生成する特徴量の数を指定することができる。
- どの変数のフラグを立てるかは、ハッシュ関数を利用して決める。
sklearn.feature_extraction.FeatureHasher
を使う。
- frequency エンコーディング
- 各水準の出現日度でカテゴリ変数を置き換える。
- target エンコーディング
- 各カテゴリの目的変数の平均値に置き換える。
- 回帰の場合 … 目的変数の平均をとる。
- 二値分類の場合 … 正例を 1、負例を 0 として平均をとる。
- 多クラス分類の場合 … クラスの数だけ二値分類があると考えて複数の特徴量を作る。
- 目的変数のリークを防ぐために、out-of-fold で自身以外の fold で平均値を計算する必要がある。 fold 数は 4〜10 程度がよい。 テストデータの変換時には学習データすべてを使って平均をとって OK。
- 各カテゴリの目的変数の平均値に置き換える。
- embedding
- 単語やカテゴリ変数を実数ベクトルに変換する。
- 自然言語処理 … Word2Vec、GloVe、fastText
- ニューラルネット … embedding layer
- embedding はニューラルネットに限らず、GBDT や線形モデルでも有効。
- 順序変数(1、2、3 や A、B、C など)
- 決定木ベースのモデルでは、1、2、3 などをそのまま入れればよい(大小関係だけが保たれていればよい)。
- その他のモデルでは、数値変数としてそのまま扱うか、カテゴリ変数として扱うかを考える。
日付・時刻
- 学習データとテストデータが時間で分けられているときは、「年」のデータをそのまま使ってはいけない。
- 「月」を周期性のあるデータとして扱うには、線形モデルでは工夫が必要。 例えば、12 月の次が 1 月ということを表現しなければならない(円形に配置するなど)。 GBDT などの決定木ベースのモデルでは、そのままつっこんでも大体うまくいく。
- 月初・月末・給料日、曜日・祝日・休日を特別扱いする。
- クリスマスやゴールデンウィーク、ブラックフライデーかどうかの二値変数。
- ある時点からの経過時間。
変数の組み合わせ
- 数値変数 × カテゴリ変数
- カテゴリごとに統計量(平均や分散)をとる。
- 数値変数 × 数値変数
- 加減乗除、余りなど。
- カテゴリ変数 × カテゴリ変数
- 組み合わせて新たなカテゴリ変数を作る。
- one-hot エンコーディングすると水準が多くなりすぎるので、target エンコーディングを適用するのがよい。
- 行の統計量
- レコードごとに複数の変数血の統計をとる。
他のテーブルの結合
- pandas の
merge()
メソッドを使う。
集約して統計量を取る
ユーザーテーブルとは別に、オンラインショップのユーザー行動ログがあるような場合。
- 単純な統計量
- カウント数 … ユーザーごとの行動ログ行数
- ユニーク数 … 購入した商品の種類、イベントの種類、利用日数
- 存在の有無 … ログインエラーの有無、特定のページの閲覧
- 合計・平均・割合 … 購入数や滞在時間
- 最大・最小・標準偏差・中央値・分移転・尖度・歪度
- 時間的な統計量
- 直近や最初のレコード
- 間隔・頻度 … 購入サイクル、閲覧頻度
- 特定のイベントからの間隔 … カートに入れてからの時間
- 順序・推移・共起・連続 … 連続する行動の組み合わせをカウント
- 条件を絞る
- 特定の種類のログに絞る
- 時間・期間をわけて集計(朝・昼・夕方・夜)
- 集計の単位を変える … ユーザーをクラスタリングしてグループで集計
- アイテム側に注目
- ユーザー側ではなくアイテムやイベント側に着目して集計
- アイテム側をグループ化(カテゴリとしてまとめる)
- 特殊商品に注目
時系列データ
- ワイドフォーマットとロングフォーマット
- ワイドフォーマット … 行 ID が「日付」、列名が「各ユーザーの ID」で、日付ごとの各ユーザーの利用時間が記録されている(つまり、「日付 x ユーザー」のテーブル)。
- ロングフォーマット … ユーザー ID、日付、利用時間を各列とする。
- 目的変数が「利用時間」である場合、ワイドフォーマットをロングフォーマットに変換してから学習する必要がある。なぜなら、機械学習モデルは各行において 1 つの目的変数を予測するものだから(ワイドフォーマットは各行に複数の目的変数が入っていることになる)。
- Reshaping and Pivot Tables
- ワイド → ロングフォーマットへの変換には
DataFrame#stack()
メソッドを使う。df_long = df_wide.stack().reset_index(1) df_long.columns = ["id", "value"]
- ロング → ワイドフォーマットへの変換には
DataFrame#pivot()
メソッドを使う。df_wide = df_long.pivot(index=None, columns="id", values="value")
- ワイド → ロングフォーマットへの変換には
- リークさせないようにするポイント
- ある時点のレコードの特徴量を作るときに、それより先の情報を使わない。
- バリデーションを行うときに、学習データにバリデーションデータより将来のレコードを含めない。
- ラグ特徴量
- 将来の売上を予測する場合、自身の直近(前日)の値が効果の大きい特徴量となる。この値は、少し前のデータの目的変数を利用した特徴量なので、「目的変数のラグ特徴量」である。
- データの周期性があるときは、「1週間前の売り上げ」のようなラグ特徴量を作ることができる。曜日ごとの特徴がある場合は効果的。
DataFrame
のshift()
メソッドを使う。x_lag1 = x.shift(1) # 1 期前 x_lag7 = x.shift(7) # 7 期前
- 目的変数以外のラグを取るのもあり。例えば、前日の天気など。
- テストデータが最新の 1 か月間で、学習データが 1 か月より前のデータの場合は、目的変数のラグ特徴量の作成に制約ができることに注意(前日のデータの目的変数を参照できないなど)。
- 移動平均
- 周期的な影響を軽減したラグ特徴量を作るには、移動平均が使える。
- 例: 1期前から3期間の移動平均
x_ma3 = x.shift(1).rolling(window=3).mean() # mean 以外 (max, median) でも可
次元削減
- 主成分分析 (PCA: Principal Component Analysis)
- 特徴量が正規分布であることが前提。画像には向いてない。
- 特異値分解 (SVD: Singular Value Decomposition) も同様に使える。
sklearn.decomposition
モジュールのPCA
やTruncatedSVD
クラスを使う。
- 非負値行列因子分解 (NMF: Non-negative Matrix Factorization)
- 非負の行列データを、より少数の要素の非負の行列の積で近似する。
- LDA: Latent Dirichlet Allocation
- ベイズ推論を用いて、各文書を確率的にトピックに分類する。
- 線形判別分析 (LDA: Linear Discriminant Analysis)
- 分類タスクについて教師ありで次元削減を行う。
- t-SNE
- 2次元平面上に圧縮して可視化する目的でよく使われる。
- UMAP
- 2018年に提案。
pip install umap-learn
でインストール。 - t-SNE より高速で、2次元や3次元を超える圧縮が可能。
- 2018年に提案。
- オートエンコーダ
- ニューラルネットを用いた次元圧縮。
- クラスタリング
- データをいくつかのグループに分ける教師なし学習。
- K-Means (Mini-Batch K-Means)、DBSCAN、Agglomerative Clustering など。
sklearn.cluster
モジュールを参照。
4. モデルの作成
モデルとは何か
- バリデーションは通常はクロスバリデーションで行う。
- テストデータの予測をするときは 2 つの方法がある
- 各フォールドで学習したモデルを使って予測して平均をとる
- 学習データ全体に対して改めてモデルを学習する
- オーバーフィッティング … 学習データだけスコアが良い。モデルが複雑すぎる
- アンダーフィッティング … 学習データでも(それ以外でも)スコアが悪い。学習不足。
- 正則化 (regularization)
- 学習時にモデルが複雑な場合に罰則を課す。
- 罰則が上回るほど予測に寄与する場合のみモデルが複雑になるため、過学習を抑えることができる。
- アーリーストッピング
- GBDT やニューラルネットなどのライブラリが備えている機能。 学習時にバリデーションスコアが上がらなくなったら学習を打ち切る。
- イテレーションごとに学習データのスコアは通常良くなり続けるが、バリデーションのスコアはある程度で改善が止まる(それ以上やると過学習になる)。
- バギング (bootstrap aggregating)
- 同じ種類のモデルを並列に複数作成 し、それぞれの予測値の平均などをとる。
- 各モデルでデータをランダムに抽出して使ったり、学習用の乱数シードを変えることで汎化性能を高める。
- ランダムフォレストはバギングを利用している。
- ブースティング (boosting)
- 複数のモデルを直列に組み合わせる。
- GBDT(勾配ブースティング)はブースティングを利用している。
GBDT の主なライブラリ
- xgboost … 2014 年公開
- 目的関数を変えると色々なタスクに対応できる。
- 回帰 … 二乗誤差
- 二値分類 … logloss
- マルチクラス分類 … multi-class logloss
- 目的関数を変えると色々なタスクに対応できる。
- lightgbm … 2016 年公開
- xgboost より高速なので現在よく使われている。
- catboost … 2017 年公開
- カテゴリ変数の扱いに工夫あり。
- 遅いのでそれほど使われていない。
ニューラルネットの主なライブラリ
- Keras/TensorFlow
- PyTorch
線形モデル
- 単体では精度が低いので、GBDT やニューラルネットにはほぼ勝てない。
- アンサンブルの 1 つのモデルや、スタッキングの最終層などに適用するのが主な使い方。
- Lasso … L1 正則化を行う線形モデル
- Ridge … L2 正規化を行う線形モデル
- 分類タスクに使うときはロジスティック回帰モデルを使う。 ロジスティック回帰では、線形回帰で求めた値にシグモイド関数を適用し、0〜1 の確率を求める。
- 線形モデルの主なライブラリ
- scikit-learn の
linear_model
モジュール- 分類タスク …
LogisticRegression
- 回帰タスク …
Ridge
- 分類タスク …
- scikit-learn の
その他のモデル
- その他のモデルはアンサンブルのモデルの 1 つとして使うのが基本。
- k 近傍法 (kNN: k-Nearest Neighbor algorithm)
- scikit-learn の
neighbor
モジュールのKNeighborsClassifier
、KNeighborsRegressor
を使う。
- scikit-learn の
- ランダムフォレスト (RF: Random Forest)
- scikit-learn の
ensemble
モジュールのRandomForestClassifier
、RandomForestRegressor
を使う。
- scikit-learn の
- ERT: Extremely Randomized Trees
- scikit-learn の
ensemble
モジュールのExtraTreesClassifier
、ExtraTreesRegressor
を使う。
- scikit-learn の
- RGF:Regularized Greedy Forest
- Regularized Greedy Forest (
rgf_python
) パッケージがある。
- Regularized Greedy Forest (
- FFM: Field-aware Factorization Machines
libffm
パッケージやxlearn
パッケージがある。- FM: Factorization Machines を発展させたモデル。
- レコメンド系のタスクと相性がよい。
その他のテクニック
- 疑似ラベリング (pseudo labeling)
- テストデータから導き出した予測値を目的変数の値とみなし、学習データに加えて再度学習する手法。
- この予測値のことを疑似ラベル (pseudo label) と呼ぶ。
- フォルダ構成について
- データサイエンスプロジェクトのディレクトリ構成をどうするか。
- Patterns for Research in Machine Learning を参考に。
5. モデルの評価
モデルの評価とは
- 汎化性能 … 未知のデータ に対する予測性能のこと。
- バリデーション … モデルの汎化性能を評価すること。
バリデーション手法
- hold-out 法 … 学習データの一部を学習に使わず、バリデーション用に取っておく。
from sklearn.model_selection import train_test_split
- データをシャッフルして分割するのが基本 (
shuffle=True
)。 - 時系列データは特殊な扱いが必要。
- データを有効活用できていないので、クロスバリデーションを使った方がいい。
- データをシャッフルして分割するのが基本 (
- クロスバリデーション … hold-out 法の手続きを複数回繰り返して学習データ全体をバリデーションの評価データとしてカバーする。
from sklearn.model_selection import KFold
- 例えば、学習データを「学習データ:評価データ=3:1」に分割してバリデーションするとき、分割位置をずらして 4 回繰り返せば学習データ全体でバリデーションできる。
- 分割されたデータを fold と呼び、分割数を fold 数と呼ぶ。
- 省略してよく CV と呼ばれる。Kaggle ではバリデーションのことを CV と言っちゃったりする(それほどバリデーションといえばクロスバリデーションということ)。
- fold 数は 4 や 5 が一般的。分割数を増やしすぎると、学習データの割合はそんなに増えないのにもかかわらず、計算時間だけ大きく増えてしまう。
- 学習データが十分に大きい場合は fold 数 = 2 や、hold-out 法でもよかったりする。
- stratified k-fold
from sklearn.model_selection import StratifiedKFold
- 層化抽出 (stratified sampling) … 分類タスクのケースで、fold ごとに含まれるクラスの割合が等しくなるようにすること。そうしないと、各分割ごとに偏った学習が行われ、評価のブレも大きくなってしまう。
KFold
クラスではsplit()
時に特徴量 (train_x
) を与えるだけでよかったが、StratifiedKFold
では目的変数 (train_y
) も与える必要がある。これは、答えとなるクラスの割合を見て分割するため。逆に、そこだけ気をつければKFold
と同様に使える。
- group k-fold
- ランダムにデータ分割すると CV の評価がよくなってしまうケースで使う。
- 例えば、テストデータに未知の顧客データが入っていて何らかの予測を行う場合は、学習時にその顧客のデータは一切使えないという前提で学習データを作らないといけない。バリデーションデータと同じ顧客のデータを学習データに入れないようにしなければいけない。
- scikit-learn に
GroupKFold
があるが、シャッフルと乱数シードの設定機能がないため使いづらい。from sklearn.model_selectioin import GroupKFold
- 顧客 ID のリストを抽出しておいて、それを
KFold
クラスで分割する、という実装をすればよい。そうすれば、ある顧客のデータが学習データと評価データの両方に混ざって入ることはなくなる。
- leave-one-out (LOO)
from sklearn.model_selection import LeaveOneOut # KFold クラスで n_splits=レコード数 とするのでも同様
- 学習データのレコード数が少ないケースで使う。
- 学習にかかる時間は少ないので、「fold 数=学習データのレコード数」にしてしまうやり方。つまり、バリデーションデータがそれぞれの分割で 1 件になるということ。
- GBDT(勾配ブースティング木)やニューラルネットで、leave-one-out +アーリーストッピングを使うとモデルの精度が過大評価されてしまう。1件1件逐次的に評価を進めていって、最も都合の良いところで学習を止めてしまうから。
時系列データのバリデーション手法
- 時系列データのタスクでは、テストデータが将来のデータになっていることが多い。
- 学習データ … 過去のデータ
- テストデータ … 将来のデータ
- バリデーションデータと同じ期間のデータを学習に使わないように注意する必要がある。同じ期間のデータがあると、周辺のデータは似通っていて予測しやすくなってしまうため。
- 時系列データの hold-out 法
<=========train==========> <===valid===> | <===test===>
- 学習データの最後の方をバリデーションデータとして学習時の評価を行う方法。
- 周期性のある時系列データの場合は、その周期を考慮して分割することを考える。
- 最終的にはバリデーションで求めた最適な特徴量やパラメータをそのまま使って、バリデーションデータも含めて再学習してモデルを完成させる(バリデーションデータはテストデータに最も近い期間のデータなので、それを学習に使わないのはもったいないため)。
- 時系列データの hold-out 法を使うときは、次のように自力で分割する。
is_tr = train_x["period"] < 3 is_va = train_x["period"] == 3 tr_x, va_x = train_x[is_tr], train_x[is_va] tr_y, va_y = train_y[is_tr], train_y[is_va]
- 時系列データのクロスバリデーション
- fold を作るときに、時間的に「学習データ → バリデーションデータ」となるように分割する。
- パターン A
fold1 <==train==> <==valid==> fold2 <=====train=====> <==valid==> fold3 <========train========> <==valid==> fold4 <===========train===========> <==valid==>
- パターン B
fold1 <==train==> <==valid==> fold2 <==train==> <==valid==> fold3 <==train==> <==valid==> fold4 <==train==> <==valid==>
- パターン A
- 基本的にバリデーションデータの期間よりも将来のデータは学習データとして使えない。ただし、問題によっては将来のデータも学習データに含めてしまった方がよいケースもある(その場合でもシャッフルして KFold 分割するのではなく、時系列ソートされたデータを KFold 分割する)。
- 時系列データでクロスバリデーションするときは、次のように分割する。
for period in [1, 2, 3] is_tr = train_x["period"] < period is_va = train_x["period"] == period tr_x, va_x = train_x[is_tr], train_x[is_va] tr_y, va_y = train_y[is_tr], train_y[is_va]
- fold を作るときに、時間的に「学習データ → バリデーションデータ」となるように分割する。
- 大まかな方針
- 時系列データが十分にあるとき … 時系列に沿ったクロスバリデーション
- 時系列データが十分でないとき … ケースバイケース
バリデーションのポイントとテクニック
- バリデーション時には、コンペでの評価指標とは異なる指標を使うこともできる。
スコアが安定しない評価指標だとモデルを改善しにくいので、より安定する指標を使った方がよい。
- 例: 二値分類のタスクでは logloss や AUC を参考にする。
- 学習データをバリデーションデータに分割するとき、学習データとテストデータの分割を真似る とよい。これは、学習データからテストデータを予測するのと同じ状況で予測していることになるから。
- 例: 学習データとテストデータが地域で分割されている場合 → バリデーションでも地域で group k-fold を行う。
- adversarial validation
- 学習データのテストデータの分布が異なるかどうかを判別する方法。
- 「学習データ+テストデータ」を使って、テストデータかどうかを二値分類するモデルを作る。
- 二値分類の AUC が 0.5 に近い場合は学習データとテストデータは見分けられない(分布が同じ)。
- 二値分類の AUC が 0.5 より十分大きい場合、テストデータを見分ける方法がある(分布が異なる)ので、学習データのうちテストデータっぽいものを優先的にバリデーションデータとするのが効果的。
6. モデルのチューニング
ハイパーパラメータのチューニング
- 用語
- ハイパーパラメータ … 学習の進め方を調整するためのパラメータ
- パラメータ空間 (parameter space) … 取り得るパラメータの組み合わせの集合
- パラメータの探索手法
- 手動でのパラメータ調整
- グリッドサーチ
- 各パラメータの候補を決めて、それらの組み合わせを全て試す。
- 組み合わせの数が多くなると非常に時間がかかる。
- scikit-learn の
model_selection.GridSearchCV
を使う。
- ランダムサーチ
- グリッドサーチのように全ての組み合わせを試すのではなく、各パラメータの候補(や範囲)の中からランダムにパラメータを組み合わせて試す。
- 試行回数を決められるので、探索時間を調整できる。
- scikit-learn の
model_selection.RandomizedSearchCV
を使う。
- ベイズ最適化 (Bayesian Optimization)
- 次に探索するパラメータをベイズ確率の枠組みで選択する。
- ライブラリとしては、optuna (2018)、hyperopt、gpyopt、spearmint、scikit-optimise など。
- hyperopt
- TPE (Tree-structured Parzen Estimator) というアルゴリズムで計算。
- このライブラリに設定する関数は、「パラメータを引数として受け取り、そにパラメータで計算したときの評価指標のスコアを返す」関数。返すスコアは低いほどよいことを示すようにする。
- optuna
- 最適化アルゴリズムは hyperopt と同じく TPE を使っているが、API が使いやすく効率化されている。
- Define-by-Run スタイル API … ハイパーパラメータ空間を別途定義せず、計算時に決まる。
- 学習曲線を見て計算を打ち切り
- 並列化による高速化
- 設定するもの
- デフォルト値は分析コンペには向いていないものもある。例えば、xgboost の eta のデフォルト値は 0.3 だが大きすぎるので、0.05 などにするとスコアが上がることがある。
- 下記用途の乱数シードは分けて定義することで過剰適合を防ぐ。
- パラメータチューニング時の fold の分割
- 実際にモデルを作成・予測するときの fold の分割
GBDT のパラメータチューニング
例えば、GBDT のライブラリである xgboost を使う場合は、次のようなパラメーターをチューニングする。
- eta … 学習率。小さいほど学習は安定するが、収束までのイテレーション数が増える。最初は 0.1 くらいにしておいて、細かい精度を競う段階で 0.01 〜 0.05 などにしていく。
- num_round … 決定木の本数。1000 や 10000 などの大きな値を設定しておき、アーリーストッピングで自動的に決めるのがよい。アーリーストッピングを観察する round 数である early_stopping_rounds は 50 程度でよい(著者の場合、10 / eta という値を使っている)。
- max_depth … 決定木の深さ。深くすると特徴量の相互作用が反映されやすくなる。モデルの複雑度が増すので過学習もされやすくなる。
- などなど…
ニューラルネットのパラメータチューニング
多層パーセプトロンでは、ネットワーク構成やオプティマイザが調整対象。 まずは、オプティマイザの学習率を調整するのがよい。 学習率の具体値は 0.00001 〜 0.01 など。 epoch 数(訓練データセット全体を何回処理したか)に応じて、loss が安定した曲線で減少していくグラフ形状になるのがよい学習率。
- ネットワーク構成(主に中間層)
- 中間層の活性化関数 … ReLU、PReLU、Leaky ReLU など。
- 中間層の層数
- 中間層のユニット数、ドロップアウト率
- Batch Normalization の有無
- オプティマイザ(ニューラルネットのウェイトの学習方法)
- SGD (Stochastic Gradient Descent) - シンプルな確率的勾配降下法。各イテレーションでミニバッチのデータを使用して勾配を計算し、パラメータを更新する。
- Adam (Adaptive Moment Estimation) - 適応的に学習率を変える。収束が速く、多くの問題に対して良好な性能を示す。
- RMSprop (Root Mean Square Propagation) … 勾配の二乗の指数移動平均を使用して学習率を調整する。Adam の前身的なアルゴリズムで、非定常な目的関数に対して効果的。
- その他
- バッチサイズ(ミニバッチのデータ数)
- Weight Decay などの正則化
線形モデルのパラメータチューニング
線形モデルでは、正則化のパラメータがチューニング対象。
- L1 正則化 (Lasso) …
alpha
パラメータを調整する。係数の大きさに比例する罰則を与える。 - L2 正則化 (Ridge) …
alpha
パラメータを調整する。係数の大きさの 2 乗に比例する罰則を与える。 - ElasticNet … L1 と L2 の両方を組み合わせた正則化。
l1_ratio
パラメータで L1 の割合を調整する。 - LogisticRegression …
C
パラメータを調整する。小さいほど正則化が強くなることに注意。
特徴選択
モデルの精度に寄与しない特徴量が多くあると制度が落ちたり、メモリ不足で学習できなかったりするので、有効な特徴量をできるだけ残したまま数を減らすのがよい。
単変量統計を用いる方法
特徴量と目的変数の関係を統計的に評価して、関係が強い特徴量を選択する。
- 相関係数 … 特徴量と目的変数の相関係数を計算して、絶対値の大きい特徴量を選択する。
- カイ二乗統計量 … カイ二乗検定の統計量の大きい方から特徴量を選択する。特徴量は非負値で分類タスクでなければならない。
特徴量の重要度を用いる方法
モデルから得られる特徴量の重要度を用いて、重要度の高い特徴量を選択する。
- ランダムフォレストの特徴量の重要度 は、分岐を作成するときの基準値の減少で計算される。
- scikit-learn の RandomForestRegressor(回帰)では「二乗誤差」
- scikit-learn の RandomForestClassifier(分類)では「ジニ不純度」
- GBDT(確率的勾配ブースティング木)の特徴量の重要度 は、各特徴量がどれだけモデルの性能に寄与しているかを示す。
- xgboost では、ゲイン(分岐による目的関数の減少)、カバー(分岐されるデータ数)、頻度(分岐に現れた回数)を出力することができる。重要度としてはゲインを見るのがよい。
- permutation importance
- 通常通り予測したときのバリデーションスコアと、ある特徴量の値をシャッフルして予測したときのバリデーションスコアの差を計算し、スコアが大きく下がったものを重要度が高いとみなす手法。モデルの種類に依存せず適用可能。eli5 というライブラリが使いやすい。モデルは使い回せるので計算に時間がかからない。
- null importance
- 目的変数をシャッフルして偽のデータセットを作り、そのデータセットで学習して特徴量の重要度を計算する。これを何度も繰り返して、本物のデータセットを使った場合の重要度と比較する。両者の重要度に大して差がないのであれば、その特徴量は重要でないとみなす。主に決定木ベースのモデルで使われる。モデルを偽のデータセットで毎回学習するので時間がかかる。特徴量間の相互作用を考慮できる可能性がある。
- boruta
- ランダムフォレストを基にした特徴量選択アルゴリズム。各特徴量の値をシャッフルした新たな特徴量 (shadow feature) を作成し、元のデータセットに結合する。ランダムフォレストで学習して各特徴量(shadow feature も含む)の重要度を計算し、shadow feature の重要度より高い特徴量のみを残す。BorutaPy というライブラリがある。
- 特徴量を大量生成して特徴選択
- 機械的な組み合わせなどで数千から数万の特徴量を作成してから特徴選択する手法。
- Kaggle で使われた例:
- 作成した大量の特徴量の中から、一部を抽出して学習データを作る。
- lightgbm で学習して、一定以上の重要度となった特徴量を採用する。
- 1 と 2 を繰り返す。
- xgbfir
- xgboost のモデルから決定木の分岐情報を抽出して、特徴量の重要度を出力するライブラリ。Gain (total_gain) から見るのがよい。
反復して探索する方法
特徴量の組を変えて学習することを繰り返し、その精度の変化を見て特徴量を探索的に選択する方法。
- Greedy Forward Selection
- 特徴量 0 個の状態からスタートし、もっともスコアを向上させる特徴量を 1 つずつ追加していく方法。スコアが向上しなくなるまで特徴量を選択&追加していく。O(n^2) の計算量になるので遅い。
- Greedy Backward Elimination
- 逆に、すべての特徴量から始め、スコアに影響のなさそうな特徴量(除いたときに最もスコアが下がらないもの)を 1 つずつ削除していく方法。
計算量を減らした簡易バージョンとしては、先に特徴量を有望な順(あるいはランダムな順)に並べておいて、その順番に追加(削除)していく方法がある。
クラスの分布が偏っている場合
二値分類において、正例が少なくほとんどが負例である場合など、クラスの分布が偏っている場合の対処法。
- アンダーサンプリング
- 負例の方が多いときに、負例の一部のみを使って学習する方法。
- 異なる負例を使って複数のモデルを学習し、それらの予測を平均する方法も有効(バギング)。
- アンダーサンプリングで学習する場合でも、特徴量の作成にはすべての負例を使うべき。
- 全てのデータで学習したモデルと比べて、精度が下がっていないことを確認すること。
- オーバーサンプリング
- 負例の方が多いときに、正例を水増しして学習する方法。
- 正例を複数回抽出する方法の他に、SMOTE (Synthetic Minority Oversampling Technique) などで正例を生成する方法がある。
- 特に工夫しない
- GBDT などのモデルを使う場合は、クラスの分布が偏っていてもうまく学習できることが多い。
- 重み付け
- クラスの分布に応じて、正例と負例の重みを調整する方法(レコードごとのウェイト設定)。
- xgboost であれば、
DMatrix
というデータ構造に変換する時にウェイトを設定する。あるいはscale_pos_weight
パラメータを使う。 - keras では
fit
メソッドの引数でウェイトを指定する。
- 確率の補正
- 評価指標が logloss などの確率の場合は、正例と負例の比率を変えた場合に確率の補正を行う必要がある。
7. アンサンブル
アンサンブルとは、複数のモデルを組み合わせてモデルを作ること。
基本的なアンサンブル
- 平均を取る
- 「異なるモデル」の予測値の平均を取る(主に回帰タスク)
- 「同一のモデル」で学習時の乱数シードを変える
- 加重平均
- 精度の高いモデルの重みを大きくする
scipy.optimize
モジュールなどを使うと、スコアが最も高くなるように重みづけを自動化できる
- 算術平均以外の平均
- 幾何平均/調和平均 … すべての値が大きいときのみ大きくなる傾向
- n乗平均を1/n乗する … いずれかの値が大きいときに大きくなる傾向
- 多数決
- 分類タスクの場合は多数決が基本
スタッキング
既存のモデルの出力から新しい特徴量を作る手法。 基本的には元の特徴量を組み合わせて新しい特徴量を作っているだけだけど、ポイントはそれが既存の学習モデルの出力から作られるということ(だからスタッキング)。
- 1 層目(下記をアンサンブルしたいモデル数だけ繰り返し、モデル数分の新しい特徴量を作る)
- out-of-fold で学習して各バリデーションデータ (fold) に対する予測値を求め、「1 層目のモデルによる予測値」という特徴量にする。
- 各 fold で学習したモデルで test データを予測し、それらを平均したものを「test データの予測値」という特徴量にする。
- 2 層目(上記を特徴量とした学習&予測)
- 各 fold において、上記の「1 層目のモデルによる予測値」から目的変数を予測するモデルを作る。
- 上記の「test データの予測値」を入力とした予測値を出す。これが最終的な予測値となる(2 層の場合)。
ようするに、既存のモデルの出力を新しい特徴量という形でアンサンブル している。 そして、そのアンサンブル方法を学習することで最適なアンサンブルを行えるようになっている(各モデルの出力をどのように多数決処理するかを自動化しているようなイメージ)。
どんなモデルをアンサンブルするか?
- アンサンブルにおいては、精度よりも多様性が重要。
- ほとんど同じ予測値を返すモデルを組み合わせても効果は低い。
- 得意な部分が違うモデルを組み合わせることで精度が上がる。
- 例: 晴れの日の販売量をよく予測できるモデルと、雨の日の販売量をよく予測できるモデル
- 例: 線形的な関係をよくとらえるモデルと、変数間の相互作用をよくとらえるモデル
- 多様なモデルを使う
- GBDT、ニューラルネット、線形モデル、k-NN(k近傍法)、ERT: Extremely Randomized Trees or ランダムフォレスト、RGF: Regularized Greedy Forest、FFM: Field-aware Factorization Machines
- 同じモデルでハイパーパラメータや使用する特徴量を変えるのもよい
- 問題の捉え方を変える
- 回帰タスクで二値分類のモデルを作る
- 多クラス運類で、一部のクラスの予測に特化したモデルを作る
- 欠損値が多いときに欠損値を予測するモデルを作る
アンサンブルの例
- 匿名化された特徴量をもとに、商品を 9 クラスに分類する問題
- スタッキングでは GBDT とニューラルネットを 2 層目で組み合わせることが重要。
- k 近傍法もスタッキングのための特徴量として役に立った。
- 生データと TF-IDF 処理したデータを並行してモデルを作った。
- 検索された語句と商品の関連度を予測する問題
- テキストの前処理 → 特徴量生成 → GBDT/ニューラルネット/線形モデルによるスタッキング
- 消費者金融の顧客の貸し倒れ率を予測する問題
- 学習データとテストデータの性質が大きく異なった。
- 学習データとテストデータが、時系列とプロジェクトによって分割されていたため、バリデーションの評価値と Public Leaderboard のスコアの整合性を取るのが難しく、スタッキングすると過学習になった。
- adversarial validation を利用した手法が有効だった(参考: 5.4.3)。
- 学習データではなくテストデータに合うようにアンサンブル時の重みを調整するために、テストデータに近い学習データをサンプリングして使用した。
- 学習データをサンプリングする割合を、Public Leaderboard のスコアとの整合性を取るために調整した。
- 「学習データのサンプリング → モデルの重みの最適化」を重みの平均値が収束するまで繰り返した。
- 学習データとテストデータの性質が大きく異なった。
感想
機械学習の様々な手法について一通り学んでみると、その基本的な考え方自体はそれほど複雑ではないという印象を受けました(もちろん、各種ライブラリの実装は先人たち知恵が詰まっていて複雑ですが)。 機械学習の課題で優れた成果を上げるには、何よりもこれらの手法に関する幅広い知識と慣れが必要ですね。 言い換えると、解法にはある程度のパターンが存在するということでもあります。 そのため、最近では生成 AI を活用した解法によってコンペティションで高得点を獲得できてしまうケースも見られるようになってきました。 今後はコンペティションの問題を作成する側にも新たな課題が生まれてきそうです。