2020年、最もお世話になった解法を紹介します

## はじめに
この記事は [kaggle advent calendar 2020](https://qiita.com/advent-calendar/2020/kaggle) 、10日目の記事です。
前日は [rinascimento741](https://qiita.com/rinascimento741) さんの [【Kaggle挫折しそうな方向け】AtCoderのススメ
](https://qiita.com/rinascimento741/items/00fa07be2e0f7e987d69) の記事でした。
明日は [tume731](https://qiita.com/tume731) さんの [wandb+Pytorch-lightning+hydraで書くNNプロジェクト](https://zerebom.hatenablog.com/entry/2020/12/11/174527) です。

## 自己紹介

Kaggle Master です。
今年の Kaggle の成績は下記の通りです。

- *OpenVaccine: COVID-19 : 847/1636*

「うわ、こいつの順位低すぎ。。」
と思った方、逆に考えてみてください。

こんな私が書く**最もお世話になった解法**、気になりませんか...？


## 解法
結論から言ってしまうと
**「target の予測値（oof） を利用して特徴を生成する」**
です。 

普段から Kaggle に取り組んでいる方には見慣れた内容かもしれませんが、
私は今年こちらの解法を Kaggle ではないコンペで活用したので、本記事のテーマとしたいと思います。


## どれぐらい使われているの？
私が参加していたコンペでは、下記が該当します。

* **Kaggle / 2019 Data Science Bowl**
 * [2nd place solution](https://www.kaggle.com/c/data-science-bowl-2019/discussion/127388)

* **Kaggle / Home Credit Default Risk**
 * [17th place solution](https://www.kaggle.com/c/home-credit-default-risk/discussion/64503)

* **atmaCup / #4**
 * 2nd place solution（私）

## どういう内容？
前述のリンク先の説明が、すでにわかりやすくまとまっているのですが、さらに例を上げると下記のようになります。

**お題**
とあるソーシャルゲームで、特定の日にユーザーがバトルイベントをプレイするか否かを予測する[^1]

**与えられたデータ**

- train
  - target が目的変数

| userId | registDate | leagueRank | power | target |
|:-:|:-:|:-:|:-:|:-:|
| 1  | 2017-08-07  |  S | 103,131  | 1  |
| 2  | 2019-03-01  | B  |  35,328 |  0 |

- test

| userId | registDate | leagueRank | power | target |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 3  | 2018-04-02  |  A | 61,722  |?|

- login
    - ユーザーのログイン履歴

| userId | date | gachaEvent | holiday |
|:-:|:-:|:-:|:-:|:-:|
|  1 | 2020-01-01  | 1  |  1 |
|  1 | 2020-01-03  | 1  |  1 |
|  1 | 2020-01-05  |  0 |  1 |
|  1 | 2020-01-10  |  1 |  0 |
|  2 | 2020-01-02  |  1 |  1 |
|  2 | 2020-01-03  | 1  |  1 |
|  2 | 2020-01-07  |  0 |  0 |
|  3 | 2020-01-04  |  1 |  1 |
|  3 | 2020-01-05  | 1  |  1 |
|  3 | 2020-01-07  | 0  |  0 |
|  3 | 2020-01-08  |  0 |  0 |

**特徴量生成**
**1, login に対して train を `merge` して、target 列を付与する。**
このとき、 userId=3 は target 不明なので、 `null` になります。

| userId | date | gachaEvent | holiday | target |
|:-:|:-:|:-:|:-:|:-:|:-:|
|  1 | 2020-01-01  | 1  |  1 |  1 |
|  1 | 2020-01-03  | 1  |  1 |  1 |
|  1 | 2020-01-05  |  0 |  1 |  1 |
|  1 | 2020-01-10  |  1 |  0 |  1 |
|  2 | 2020-01-02  |  1 |  1 |  0 |
|  2 | 2020-01-03  | 1  |  1 |  0 |
|  2 | 2020-01-07  |  0 |  0 |  0 |
|  3 | 2020-01-04  |  1 |  1 |   |
|  3 | 2020-01-05  | 1  |  1 |   |
|  3 | 2020-01-07  | 0  |  0 |   |
|  3 | 2020-01-08  |  0 |  0 |   |


**2, login の target を予測するモデルを作る**

target が存在する userId=1 or 2 のデータを学習データとして
Cross Validation を行い、モデルを作ります。

userId=1 or 2 のデータに対しては oof ( Out Of Fold ) の値を付与します。
userId=3 のデータに対しては、各 Fold で作成したモデルで predict した値の平均値を付与します。

| userId | date | gachaEvent | holiday |target |predictions |
|:-:|:-:|:-:|:-:|:-:|:-:|
|  1 | 2020-01-01  | 1  |  1 |  1 |  0.22 |
|  1 | 2020-01-03  | 1  |  1 |  1 |  0.18 |
|  1 | 2020-01-05  |  0 |  1 |  1 |  0.14 |
|  1 | 2020-01-10  |  1 |  0 |  1 |  0.11 |
|  2 | 2020-01-02  |  1 |  1 |  0 |  0.05 |
|  2 | 2020-01-03  | 1  |  1 |  0 |  0.06 |
|  2 | 2020-01-07  |  0 |  0 |  0 |  0.08 |
|  3 | 2020-01-04  |  1 |  1 |   |  0.11 |
|  3 | 2020-01-05  | 1  |  1 |   |  0.12 |
|  3 | 2020-01-07  | 0  |  0 |   |  0.07 |
|  3 | 2020-01-08  |  0 |  0 |   |  0.05 |

**3, 予測値の集約特徴を作り、元の train, test に戻す**

例えば、最小値、平均値、最大値をとって特徴量とすることができます。

- train

| userId | registDate | leagueRank | power | target |predictions_min |predictions_mean |predictions_max |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 1  | 2017-08-07  |  S | 103,131  | 1  | 0.11|0.1625 |0.22 |
| 2  | 2019-03-01  | B  |  35,328 |  0 | 0.05| 0.0633| 0.08|

- test

| userId | registDate | leagueRank | power | target |predictions_min |predictions_mean |predictions_max |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 3  | 2018-04-02  |  A | 61,722  |?|0.05| 0.0875| 0.12|

## なんで上手くいくの？

前述の例であれば、他の方法（例えば、単に login から集約特徴を生成）よりも、効率よく情報を取り出せるため、というのが所感です。
TargetEncoding にも近い感覚ですが、なんだかんだリークしたり上手くいかない場合もあるので、oof を利用するのは理にかなっているのではないでしょうか。

## 適用できる場面が少ないのでは？

ここまでは target の oof を見てきましたが、応用して、重要度の高い説明変数の oof を利用するというやり方もあります。（特に、重要度が高いが null が多い説明変数に効果的です）

* **Kaggle / PLAsTiCC Astronomical Classification**
 * [2nd place solution](https://www.kaggle.com/c/PLAsTiCC-2018/discussion/75059)
 * 5 で「hostgal_specz」という変数の oof を利用した旨が書かれています

* **Kaggle / Home Credit Default Risk**
 * [3rd place solution](https://www.kaggle.com/c/home-credit-default-risk/discussion/64596)
 * 「ext_sources」という変数を予測するモデルを作成した旨が書かれています

* **bitgrit / SwipeToSuccess**
 * 3rd place solution（私）


## まとめ
Kaggle で度々使われている**「target の予測値（oof） を利用して特徴を生成する」**方法とその応用についてご紹介しました。Kaggle ではないコンペですが、私が今年お世話になったやり方です。

最近は時系列テーブルコンペでも Transformer の勢いを感じますが、今回のような方法も機会があれば試してみてはいかがでしょうか。


[^1]: もちろんダミーデータです