kaggleで勝つデータ分析の技術 読了

これなーに

www.amazon.co.jp

上記書籍を読んだ。
面白かったポイントを列挙しておく。

不均衡データの評価指標はPR-AUCが良さそう

正例が1%、負例が99%のような2値分類におけるROC-AUCだと、True-Negative(負例を負として当てた)が多くなって正例が正しく当てられなくても数値が大きくなってしまう。
一方PR-AUCだとTrue-Positive(正例を正として当てた)を重視するPrecisionとRecallでの評価になるので、1%の発生をどれだけ的確に当てられたかの評価になるので良さそう。

PR-CURVE

TP/FN/TN/FP(いつも忘れる・・・)

PrecisionとRecall

この辺も読んでてわかりやすかった。
不均衡データとの向き合い方 - Qiita

GBDTに変数のスケール変換は不要

変数のスケール(変数の値が取りうる範囲)や分布をあまり気にしなくて良く、また欠損値やカテゴリ変数を扱いやすいことが、GBDTがよく使われている理由の1つです。

ですな〜 分岐が大小関係で行われるから、対数取ったりは特にしなくてよいのは楽だよね。

特徴量作成について

モデルに現在与えられている入力から読み取れない・読み取りづらい情報を追加で与えるというのが、特徴量を作るイメージになります。

「決定木の気持ちになって考える。」って言葉良いね。

ユーザの行動に大きく影響している要素が、購入額を購入数で除した平均購入単価だったとします。このとき、購入額と購入数のみが特徴量として入っていると、GBDTは購入額と購入数の相互作用として平均購入単価をある程度までは反映しますが、明示的に特徴量として加えた方がより適切に反映します。

すなわち、例えば購入金額と購入回数のカラムがあった場合、この二つを特徴量として利用すると、木の分岐で「購入平均単価」みたいな意味合いが一応表現はできるけど、直接的に購入金額/購入回数で新しい特徴量を作った方が良いわけね。
確かに木の分岐回数の最大値を超えちゃうと表現できなかったりするしね。

GBDTでは欠損値をそのまま使った方が良い

埋めたほうが良い場合もあるけど、そのままで良いみたい。欠損は欠損として扱う。

欠損値の埋め方

上と矛盾するけど埋め方の考え方でほ〜ってなった。

また、平均のとり方も、単純に全データの平均ではなく、別のカテゴリ変数の値でグループ分けし、そのグループごとの平均を代入する方法も考えられます。これは、欠損している変数の分布がグループごとに大きく変わることが想定される場合に有効です。カテゴリ変数の値ごとに平均をとる際に、データ数が極端に少ないカテゴリが存在する場合、その平均値にはあまり信用がおけませんし、そのカテゴリの値はすべて欠損しているかもしれません。そのような場合、以下の算式のように分子と分母に定数項を足して計算させるBayesianaverageという方法が

確かに、グループで大きく分かれる特徴量についてはこういうのが良いかも。
例えば、仕事の給与とかだと業界や職種で結構差がありそうで、そういうグループでの平均などで欠損を埋めてもいいかも。
欠損値そのまま利用で良いってあるけど、特徴量の重要度が高いなら埋め方を検討してみるのもいいかもね。

カテゴリ変数の場合は、欠損値を1つのカテゴリとみなして欠損を表すカテゴリを新たに作り置き換える方法や

カテゴリ変数の欠損は、欠損カテゴリにしても良いかもですって。

年齢や「最初に予約した日と年齢や「最初に予約した日とアカウント作成日の差をbinningしカテゴリ変数としたもの」などの重要と思われる特徴量について、予測による補完が行われています

なるほど〜 やっぱり重要な特徴量が欠損している場合は、予測してでも埋めた方が良いこともあるのね。

欠損として扱うかどうかを考える

最初の段階で変数の分布をヒストグラムなどで見て、欠損として認識すべき値がないかを確認しておくことが望ましいでしょう。

これは確かに大切そう。欠損扱いでルールで一定の値を入れている場合のデータがあるから、それを欠損とみなすのを発見しとくといいよねってことね。

binning

データに対する前提知識があり、どのような区間に分けるべきかの見当がついているとより有効です。

例えば、年齢を~18/19~22/23~とかの高校生/大学生/社会人とかにしとくと意味あるね的なのが分かっていたら、それを特徴量にしても良いってことか。

Kaggleの「CouponPurchasePrediction」の筆者(T)のソリューションでは、食事のクーポンの単価を1,500円以下、1,500円~3,000円、3,000円以上にbinningし、そのあとそれらの区間ごとに他の変数の値の集計を行っていました。同じ食事でも金額の範囲ごとに利用目的が異なることを反映しています。

具体例。

target encoding

カテゴリ変数を数値に変える方法。
CVのk-foldで自分のデータがないfoldで目的変数の確率を平均化して使う。

xfeafってのを使うと簡単にできるらしい。

from sklearn.model_selection import KFold

fold = KFold(n_splits=5, shuffle=False)
encoder = TargetEncoder(
    input_cols=["Cabin"], 
    target_col="Survived",
    fold=fold,
    output_suffix="_re"
    )

encoded_df = encoder.fit_transform(train_df)
encoded_df[["Survived", "Cabin", "Cabin_re"]].head(3)

acro-engineer.hatenablog.com

xfeat色々できて楽そう。さすがPFN...
Python: xfeat を使った特徴量エンジニアリング - CUBE SUGAR CONTAINER

レコード間の関係性に注目

一方で、レコード間に一部強い関係性が見られるデータもあります。分かりやすい例としては、同じユーザのレコードが複数ある場合が考えられます。
数個のレコードの関係性に注目することも、レコード全体としてどういうパターンがあるかに注目することもできます。例えば、上記の同じユーザのレコードが複数ある例で、出現している回数が違うことが何らかの性質を表していると考えて、ユーザごとのレコード数をカウントすることができます。

なるほど!全体でカウント取ってそれを特徴量とするのね。確かに熱度みたいな意味合いとかになるかも。

あるユーザの値とそのユーザの属するグループの平均値との差や比率をとるなど、他と比較したときの差や比率といった相対値に注目するのも有効です。

これもいいね。平均からの差で偏差値的なものが見えてくるかも。

トピックモデルの応用によるカテゴリ変数の変換

トピックモデルという文書分類の手法を応用して、他のカテゴリ変数との共起の情報から、カテゴリ変数を数値ベクトルに変換する手法があります。2つのカテゴリ変数の片方を文書、もう片方を単語とみなすと、各文書に各単語が何回現れたかという共起の情報から単語文書のカウント行列を作ることができます。これに対しLDA(latentDirichletallocation)を適用すると、前者の変数を文書が属するトピックに対する確率を表す数値ベクトルに変換できます。

なるほど、ちゃんと理解してない・・・。

Negative down sampling

これ良さそう!
正例1%、負例99%のようなデータなら、負例1%をランダムサンプリングして作っちゃえば良いじゃん。
そして負例のサンプリングはランダムに何回もできて、それらのモデルでバギング。

ユーザーの行動から特徴量作成

想像力が必要ですが、極めて有効な特徴量を抽出できることがあります。このコンペでは、以下のような特徴量などが作成されました。
1.ユーザの「真面目さ」を表現する:訪問日数や動画視聴数など、ユーザの「真面目さ」を表現する特徴量
2.ユーザの学習の進捗を表現する:アクセスログから、ユーザの進捗度合、平均的な進捗度合とのずれが算出できるため、それらを特徴量とする

こういうの大切だよね。

GBDTについて

label-encodingは必要だけど、one-hot encodingは不要。

例えばあるカテゴリ変数cが1から10まであるときに、cが5のときのみ効く特徴だった場合に、決定木の分岐を(c<5,5<=c)と(c<=5,5<c)と重ねることで、cが5であるという特徴が抽出されるためです。

なるほど・・・。

catboost

targetencodingを行い、数値に変換します。targetencodingは使い方を誤ると目的変数の情報を不適切に使ってしまうため、ランダムにデータを並べ変えながら適用するなどの工夫がされています。

マジか・・・

過学習を防ぐテクニック

データが少なすぎる葉を構成しない

決定木の深さの制限

adversarial validation

学習データとテストデータを結合し、テストデータか否かを目的変数とする二値分類を行うことで、学習データとテストデータの分布が同じかどうかを判断する手法があります。同じ分布であればそれらの見分けはできないので、その二値分類でのAUCは0.5に近くなります。一方、AUCが1に近くなった場合は、それらをほぼ確実に見分けられる情報があることになります。上記の二値分類でAUCが0.5を十分上回るような、学習データとテストデータが違う分布の場合を考えます。このとき、「テストデータらしい」学習データをバリデーションデータとすることで、テストデータを良く模したデータでの評価を期待できます。

ほ〜〜〜ほ〜〜〜って感じ。なるほど、今の実務では使わなさそうだけど、覚えておきたいテクニックかも。

ハイパラチューニング

1.以下のパラメータを初期値に設定する
・eta:0.1or0.05(データ量に依存する)
・max_depth:最初にチューニングするので決めない
・colsample_bytree:1.0
・colsample_bylevel:0.3
・subsample:0.9
・gamma:0
・lambda:1
・alpha:0
・min_child_weight:1
2.depthの最適化
・5~8ぐらいを試す。さらに浅いor深い方が改善しそうなら広げる
3.colsample_levelの最適化
・0.5~0.1を0.1刻みで試す
4.min_child_weightの最適化
・1,2,4,8,16,32,...と2倍ごとに試す
5.lambda,alphaの最適化
・両者のバランスなのでいろいろ試す(初期ではやらないこともある)

ありがテェ・・・・

feature importance

ここで、(Pythonの)デフォルトでは頻度が出力されますが、ゲインを出力した方が良いでしょう。ゲインの方が、特徴量が重要かどうかをより表現していると考えられるためです。

デフォルトは頻度らしいけど、ゲインは目的変数の精度への寄与なので、ゲイン指定で見ること。

以上! 面白いなぁ