Kaggleコンペで出されるタスクと評価指標まとめてみた

# コンペにおけるタスクの種類

- 何を予測するのか？
- どういう予測値を提出するのか？

## 回帰タスク

**数値**を予測する
- モノの値段
- 株価のリターン
- 店舗への来客数

評価指標：
- RMSE
- MAE

コンペ例
- [House Prices - Advanced Regression Techniques](https://www.kaggle.com/c/house-prices-advanced-regression-techniques)
- [Zillow Prize: Zillow’s Home Value Prediction (Zestimate)](https://www.kaggle.com/c/zillow-prize-1)

## 分類タスク

### 二値分類

**レコードがある属性に属しているかどうか**を予測する
- 患者が病気にかかっているか否か

2つのケースがある
1. 0 or 1 の2種類のラベルで予測を提出
2. 0から1の間の確率を表す数値で予測を提出

評価指標：
- 1 の場合、F1-score
- 2 の場合、loglossやAUC

コンペ例
- [Titanic - Machine Learning from Disaster](https://www.kaggle.com/c/titanic)
- [Home Credit Default Risk](https://www.kaggle.com/c/home-credit-default-risk)

### 多クラス分類

#### マルチクラス分類

**レコードが複数のクラスのうちどれか1つに属しているかどうか**を予測する

評価指標：
- multi-class logloss

#### マルチラベル分類

**同時に複数のクラスに属する場合がある**
二値分類をクラスの数だけ繰り返すのが基本的な解法

評価指標：
- mean-F1
- macro-F1

それぞれ2つのケースがある
1. 属していると予測されるクラスのラベルを提出
2. 各クラスに属している確率を0から1の間の数値で提出

コンペ例
- [Two Sigma Connect: Rental Listing Inquiries](https://www.kaggle.com/c/two-sigma-connect-rental-listing-inquiries)
- [Human Protein Atlas Image Classification](https://www.kaggle.com/c/human-protein-atlas-image-classification)

## レコメンデーション

**ユーザが購入しそうな商品や反応しそうな広告など**を予測する
ユーザごとに商品や広告を複数個予測するケースが主になる

商品レコメンデーションの場合、2つのケースがある
1. 購入可能性に応じた順位をつけて予測を提出する場合
2. 順位をつけずに提出する場合（マルチラベル分類のように、複数の正解があり複数の予測値を提出するタスク）

いずれの場合でも、各ユーザが各商品を購入するかしないかという二値分類問題として解くのが一般的
二値分類によって予測した各ユーザの各商品の購入確率をもとに、上位の商品から予測値として提出する

評価指標：
- 1の場合、MAP@K
- 2の場合、マルチラベル分類と同様のもの

コンペ例
- [Santander Product Recommendation](https://www.kaggle.com/c/santander-product-recommendation)
- [Instacart Market Basket Analysis](https://www.kaggle.com/c/instacart-market-basket-analysis)

## その他のタスク

### 物体検出

**画像に含まれる物体のクラスとその存在する領域を矩形領域で推定する**

コンペ例
- [Google AI Open Images - Object Detection Track](https://www.kaggle.com/c/google-ai-open-images-object-detection-track)

### セグメンテーション

**画像に含まれる物体の存在領域を画像のピクセル単位で推定する**

コンペ例
- [TGS Salt Identification Challenge](https://www.kaggle.com/c/tgs-salt-identification-challenge)

# 評価指標（evaluation metrics）

**学習させたモデルの性能やその予測値の良し悪しを測る指標**

## 回帰タスクにおける評価指標

下記の数式はこれらの表記に従う
- $N$: レコード数
- $i = 1, 2, ..., N$: 各レコードのインデックス
- $y_i$: $i$番目のレコードの真の値
- $\hat{y_i}$: $i$番目のレコードの予測値

### RMSE（Root Mean Squared Error：平均平方二乗誤差）

計算方法：
**各レコードの目的変数の真の値と予測値の差の二乗を取り、それらを平均した後に平方根をとる**

```math
RMSE=\sqrt{\frac{1}{N}\sum_{i=1}^{N}(y_i-\hat{y_i})^2}
```

ポイント
- RMSEを最小化した場合に求まる解が、誤差が正規分布に従うという前提のもとで求まる最尤解と同じになる
- 仮に1つの代表値で予測を行う場合、RMSEを最小化する予測値は平均値
- MAEに比べると外れ値の影響を受けやすいので、あらかじめ外れ値を除く処理などをしておいて、外れ値に過剰に適合したモデルを作成してしまう可能性を防ぐと良い

```python:計算例
import numpy as np
from sklearn.metric import mean_squared_error

y_true = [1.0, 1.5, 2.0, 1.2, 1.8]
y_pred = [0.8, 1.5, 1.8, 1.3, 3.0]

rmse = np.sqrt(mean_squared_error(y_true, y_pred))
print(rmse)
# 0.5531726674375732
```

コンペ例
- [Elo Merchant Category Recommendation](https://www.kaggle.com/c/elo-merchant-category-recommendation)

### RMSLE（Root Mean Squared Logarithmic Error）

計算方法：
**真の値と予測値の対数をそれぞれ取った後の差の二乗の平均の平方根**

```math
RMSLE=\sqrt{\frac{1}{N}\sum_{i=1}^{N}(\log(1+y_i)-\log(1+\hat{y_i}))^2}
```

- $\log x$は$x$の自然対数（$\log_e x$）

ポイント
- 目的変数の対数をとって変換した値を新たな目的変数とした上でRMSEを最小化すれば、RMSLEを最小化することになる
- 目的変数が裾の重い分布を持ち、変換しないままだと大きな値の影響が強い場合や、真の値と予測値の比率に着目したい場合に用いられる。上式の二乗括弧内の式において、$\log(1+y_i)-\log(1+\hat{y_i})=\log\frac{1+y_i}{1+\hat{y_i}}$となることからも、この指標は比率に着目していることがわかる
- 対数を取るにあたり、真の値が0の時に値が負に発散するのを避けるため、通常は1を加えてから対数を取る。これには、numpyの`log1p`関数が使える
- scikit-learnのmetricsモジュールにある`mean_squared_log_error`を用い計算できる。

コンペ例
- [Recruit Restaurant Visitor Forecasting](https://www.kaggle.com/competitions/recruit-restaurant-visitor-forecasting)

### MAE（Mean Absolute Error）

計算方法：
**真の値と予測値の差の絶対値の平均**

```math
MAE=\frac{1}{N}\sum_{i=1}^{N}\left|y_i-\hat{y_i}\right|
```

ポイント
- 外れ値の影響を低減した形での評価に適した関数
- $\hat{y_i}$による微分が$\hat{y_i}=y_i$で不連続だったり、二次微分が常に0になってしまうという扱いづらい性質を持っている
- 仮に1つの代表値で予測を行うとき、MAEを最小化する予測値は中央値。
- scikit-learnのmetricsモジュールにある`mean_absolute_error`を用い計算できる。

コンペ例
- [Allstate Claims Severity](https://www.kaggle.com/c/allstate-claims-severity)

### 決定係数

計算方法：
```math
\begin{align}
R^2&=1-\frac{\sum_{i=1}^{N}(y_i-\hat{y_i})^2}{\sum_{i=1}^{N}(y_i-\bar{y})^2}\\
\bar{y}&=\frac{1}{N}\sum_{i=1}^{N}y_i
\end{align}
```

ポイント
- 回帰分析の当てはまりの良さを表す
- 分母は予測値に依らず、分子は二乗誤差を差し引いているため、決定係数を最大化することはRMSEを最小化するのと同義
- 決定係数は最大で1を取る、1に近づくほど精度の高い予測ができている事になる
- scikit-learnのmetricsモジュールの`r2_score`を用い計算できる

コンペ例
- [Mercedes-Benz Greener Manufacturing](https://www.kaggle.com/c/mercedes-benz-greener-manufacturing)

## 二値分類における評価指標：正例か負例かを予測値とする場合

### 混同行列（confusion matrix）

予測値と真の値の組み合わせには4つある
- **TP(True Positive、真陽性)**：予測値が正例、予測が正しい
- **TN(True Negative、真陰性)**：予測値が負例、予測が正しい
- **FP(False Positive、偽陽性)**：予測値が正例、予測が誤り
- **FN(False Negative、偽陰性)**：予測値が負例、予測が誤り

```python:計算例
import numpy as np
from sklearn.metrics import confusion_matrix

# 正例が1、負例が0
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]

tp = np.sum((np.array(y_true) == 1) & (np.array(y_pred) == 1))
tn = np.sum((np.array(y_true) == 0) & (np.array(y_pred) == 0))
fp = np.sum((np.array(y_true) == 0) & (np.array(y_pred) == 1))
fn = np.sum((np.array(y_true) == 1) & (np.array(y_pred) == 0))

# numpy行列で混同行列を作る場合
confusion_matrix1 = np.array([[tp, fp], [fn, tn]])
print(confusion_matrix1)
# [[3 1]
#  [2 2]]

# scikit-learnを使う場合
# 混同行列の要素の配置が違うのに注意
confusion_matrix2 = confusion_matrix(y_true, y_pred)
print(confusion_matrix2)
# [[2 1]
#  [2 3]]
```

### `accuracy`(正答率)と`error rate`(誤答率)

計算方法：
正解のレコード数を全てのレコード数で割る

```math
\begin{align}
accuracy&=\frac{TP+TN}{TP+TN+FP+FN}\\
error \, rate&=1-accuracy
\end{align}
```

ポイント
- 不均衡なデータの場合はモデルの性能を評価しづらく、分析コンペではあまり使用されない

```python:計算例
from sklearn.metrics import accuracy_score

# 正例が1、負例が0
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]

accuracy = accuracy_score(y_true, y_pred)
print(accuracy)
# 0.625
```

コンペ例
- [Text Normalization Challenge - English Language](https://www.kaggle.com/c/text-normalization-challenge-english-language)

### `precision`（適合率）と`recall`（再現率）

計算方法：
- `precision`は正例と予測したもののうち、真の値も正例の割合
- `recall`は真の値が正例のもののうち、どの程度を正例の予測として含められているかの割合

```math
\begin{align}
accuracy&=\frac{TP}{TP+FP}\\
recall&=\frac{TP}{TP+FN}
\end{align}
```

- それぞれ0~1の値を取る。1に近づくほど良いスコア
- `precision`と`recall`は互いにトレードオフ。どちらかの値を高くしようとすると、もう一方の値は低くなる
- 片方の指標を無視すれば、もう片方の指標を1に近づけられるので、単体として分析コンペの指標になることはない
- 誤検知を少なくしたい=>`precision`重視、正例の見逃しを避けたい=>`recall`重視
- scikit-learnのmetricsモジュールの`precision_scrore`, `recall_score`が使える

### `F1-score`と`Fβ-score`

計算方法：
- `F1-score`は`precision`と`recall`の調和平均。バランスを取った指標なので、実務でもよく使われる。F値とも呼ばれる。
- `Fβ-score`は`F1-score`から`recall`をどれだけ重視するかを表す係数`β`により調整した指標。コンペでは`F2-score`の採用例がある。

```math
\begin{align}
F_1&=\frac{2}{\frac{1}{recall}+\frac{1}{precision}}\\
&=\frac{2 \cdot recall \cdot precision}{recall+precision}\\
&=\frac{2TP}{2TP+FP+FN}\\
F_{\beta}&=\frac{(1+\beta^2)}{\frac{\beta^2}{recall}+\frac{1}{precision}}\\
&=\frac{(1+\beta^2) \cdot recall \cdot precision}{recall+\beta^2precision}
\end{align}
```

- `F1-score`は分子にTPのみが含まれることからわかるように、正例と負例について対称に扱っていない。真の値と予測値の正例と負例をともに入れ替えると、スコアや振る舞いが変わる。
- scikit-learnのmetricsモジュールの`f1_score`, `fbeta_score`を用いて計算できる

コンペ例
- [Quora Insincere Questions Classification](https://www.kaggle.com/c/quora-insincere-questions-classification)

### MCC(Matthews Correlation Coefficient)

計算方法：
```math
MCC=\frac{TP \times TN - FP \times FN}{\sqrt{(TP+FP)(TP+FN)(TN+FP)(TN+FN)}}
```

- 不均衡なデータに対してモデルの性能を適切に評価しやすい
- -1~+1の範囲の値を取り、+1の時に完璧な予測、0の時にランダムな予測、-1の時に完全に反対の予測を行なっている事になる
- `F1-score`と違い正例と負例を対称に扱っているため、真の値と予測値の正例と負例を入れ替えてもスコアは同じ
- scikit-learnのmetricsモジュールの`matthews_corrcoef`を用いて計算できる

コンペ例
- [Bosch Production Line Performance](https://www.kaggle.com/c/bosch-production-line-performance)

## 二値分類における評価指標：正例である確率を予測値とする場合

### `logloss`

`cross entropy`と呼ばれることもある

下記の数式は以下の表記に従う
- $N$: レコード数
- $i = 1, 2, ..., N$: 各レコードのインデックス
- $y_i$: $i$番目のレコードが正例かどうかを表すラベル（正例=1, 負例=0）
- $p_i$: $i$番目のレコードが正例である予測確率
- $p_i'$: 真の値を予測している確率（真の値が正例=$p_i$, 負例=$1-p_i$）

```math
\begin{align}
logloss&=-\frac{1}{N}\sum_{i=1}^N(y_i \log p_i+(1-y_i)\log (1-p_i))\\
&=-\frac{1}{N}\sum_{i=1}^N\log p_i'
\end{align}
```

- 数値が低い方が良い指標
- そのレコードが正例である確率を低く予測したにもかかわらず正例である場合や、正例である確率を高く予測したにもかかわらず負例である場合、大きなペナルティが与えられる（数値が大きくなる）
- レコード$i$についてのスコア$L_i=-(y_i\log p_i+(1-y_i)\log(1-p_i))$を予測確率$p_i$で微分すると、$\frac{\partial L_i}{\partial p_i}=\frac{p_i-y_i}{p_i(1-p_i)}$となり、$p_i=y_i$の時に$L_i$が最小となる。つまり、確率を正しく予測できている時に`logloss`の値は最小。
- モデルを学習するときの目的関数としてもよく使われる

```python:計算例
from sklearn.metrics import log_loss

# 正例が1、負例が0の真の値と予測確率
y_true = [1, 0, 1, 1, 0, 1]
y_prob = [0.1, 0.2, 0.8, 0.8, 0.1, 0.3]

logloss = log_loss(y_true, y_prob)
print(logloss)
# 0.7135581778200728
```

コンペ例
- [Quora Question Pairs](https://www.kaggle.com/c/quora-question-pairs)

### AUC(Area Under the ROC Curve)

計算方法：
ROC曲線（Receiver Operating Characteristic Curve）を元に算出される
ROC曲線は、予測値を正例とする閾値を1から0に動かし、そのときの偽陽性率/真陽性率を$(x, y)$としてプロットすることで描ける。
ここの真陽性率は`recall`（再現率）と同じ定義。
ROC曲線の下部の面積がAUCとなる。

![auc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735334/aeb975a0-667f-5cef-c2f6-e989e1564a2e.png)
[画像出典](https://qiita.com/osapiii/items/a2ed9f638b51f3b22cd6)

- x軸=偽陽性率：誤って正例と予測した負例が、全体の負例のうちに占める割合（混同行列の要素では、$\frac{FP}{FP+TN}$）
- y軸=真陽性率：正しく正例と予測した正例が、全体の正例のうちに占める割合（混同行列の要素では、$\frac{TP}{TP+FN}$）

AUCの性質
- 完全な予測を行なった場合には、ROC曲線は左上の$(0.0, 1.0)$の点を通り、AUCは1となる
- ランダムな予測の場合には、ROC曲線は概ね対角線を通り、AUCは0.5程度となる
- 予測値を反対にした場合、AUCは$1.0-元のAUC$となる
- AUCは「正例と負例をそれぞれランダムに選んだ時に、正例の予測値が負例の予測値より大きい確率」としても定義できる。（2つの予測値が同率の場合は考慮せずに）式で表すと、
```math
AUC=\frac{(y_i=1, y_j=0, \hat{y_i}>\hat{y_j})である(i, j)の組の個数}{(y_i=1, y_j=0)である(i, j)の組の個数}
```
- 上記の式を使うと、どの程度の予測を改善するとどの程度AUCが上がるかを考えやすい
- AUCは各レコードの予測値の大小関係のみが値に影響する。必ずしも予測値は確率でなくても構わない。各モデルの予測値をアンサンブルするときに、予測確率を順位に変換した平均が使われることがある
- 正例が非常に少ない不均衡データの場合、正例の予測値をどれだけ高確率の側に寄せるかがAUCに大きく影響する。負例の予測値の誤差の影響はあまり大きくない。
- Gini係数は$Gini=2 \cdot AUC - 1$で表され、AUCと線形関係にある。評価指標がGini係数の場合はAUCであるのとほぼ同義
- scikit-learnのmetricsモジュールの`roc_auc_score`で計算できる

コンペ例
- [Home Credit Default Risk](https://www.kaggle.com/c/home-credit-default-risk)

## 多クラス分類における評価指標

### multi-class accuracy

計算方法：
予測が正解であるレコード数を全てのレコード数で割ったもの

- 二値分類の`accuracy`を多クラスへ拡張したもの。予測が正しい割合を表す。
- 二値分類と同じく、scikit-learnのmetricsモジュールの`accuracy_score`を使い計算できる。

### multi-class logloss

計算方法：
各クラスの予測確率を提出し、レコードが属するクラスの予測確率の対数をとり符号を反転させた値

下記の数式は以下の表記に従う
- $M$: クラス数
- $y_{i,m}$: レコード$i$がクラス$m$に属する場合は1、そうでない場合は0
- $p_{i,m}$: レコード$i$がクラス$m$に属する予測確率

```math
multiclass\,logloss=-\frac{1}{N}\sum_{i=1}^N\sum_{m=1}^My_{i,m}\log p_{i,m}
```

- `logloss`をマルチクラス分類に拡張したもの
- 予測値はレコード数×クラス数の行列で提出する
- レコードが属するクラスの確率を低く予測してしまうと大きなペナルティが与えられる
- 各レコードに対して予測確率の合計は1になる必要がある。そのようになっていない場合は評価指標の計算において自動的に調整される
- scikit-learnのmetricsモジュールの`log_loss`を使い計算できる。二値分類とは`log_loss`関数に与える予測値の配列の形が異なる

```python:計算例
import numpy as np
from sklearn.metrics import log_loss

# M=3 のクラス分類の真の値と予測値
y_true = np.array([0, 2, 1, 2, 2])
y_pred = np.array([[0.68, 0.32, 0.00],
                   [0.00, 0.00, 1.00],
                   [0.60, 0.40, 0.00],
                   [0.00, 0.00, 1.00],
                   [0.28, 0.12, 0.60]])
logloss = log_loss(y_true, y_pred)
print(logloss)
# 0.3625557672904274
```

### `mean-F1`、`macro-F1`、`micro-F1`

計算方法：
- `mean-F1`: レコード単位で`F1-score`を計算し、その平均値が評価指標のスコア
- `macro-F1`: 各クラスごとの`F1-score`を計算し、それらの平均値が評価指標のスコア
- `micro-F1`: レコードxクラスのペアのそれぞれに対しTP, TN, FP, FNのどれに当てはまるかカウントし、その混同行列に基づきF値を計算したものが評価指標のスコア

ポイント
- `F1-score`を多クラス分類に拡張したもの。主にマルチラベル分類で用いられる評価指標
- `macro-F1`はそれぞれのクラスで二値分類を行い、それらの`F1-score`を平均しているのと同じ。マルチラベル分類ではそれぞれのクラスで独立に閾値を最適化できる
- 各クラスのレコードが不均衡な場合など、これらの指標の振る舞いは異なる。どれが採用されるかはコンペ主催者がどんな観点から評価したいかによる

```python:計算例
import numpy as np
from sklearn.metrics import f1_score

# 真の値 [[1, 2], [1], [1, 2, 3], [2, 3], [3]]
y_true = np.array([[1, 1, 0],
                   [1, 0, 0],
                   [1, 1, 1],
                   [0, 1, 1],
                   [0, 0, 1]])

# 予測値 [[1, 3], [2], [1, 3], [3], [3]]
y_pred = np.array([[1, 0, 1],
                   [0, 1, 0],
                   [1, 0, 1],
                   [0, 0, 1],
                   [0, 0, 1]])

mean_f1 = np.mean([f1_score(y_true[i, :], y_pred[i, :]) for i in range(len(y_true))])

n_class = 3
macro_f1 = np.mean([f1_score(y_true[:, c], y_pred[:, c]) for c in range(n_class)])

micro_f1 = f1_score(y_true.reshape(-1), y_pred.reshape(-1))

print(mean_f1, macro_f1, micro_f1)
# 0.5933333333333334 0.5523809523809523 0.6250000000000001

# scikit-learnのメソッドを使う場合
mean_f1 = f1_score(y_true, y_pred, average='samples')
macro_f1 = f1_score(y_true, y_pred, average='macro')
micro_f1 = f1_score(y_true, y_pred, average='micro')
print(mean_f1, macro_f1, micro_f1)
# 0.5933333333333334 0.5523809523809523 0.6250000000000001
```

コンペ例
- [Instacart Market Basket Analysis](https://www.kaggle.com/c/instacart-market-basket-analysis)
- [Human Protein Atlas Image Classification](https://www.kaggle.com/c/human-protein-atlas-image-classification)

### `quadratic weighted kappa`

計算方法：
下記の数式は以下の表記に従う
- $O_{i, j}$: 真の値のクラスが$i$, 予測値のクラスが$j$のレコード数。これを行列の形に並べると多クラスでの混同行列となる
- $E_{i, j}$: 真の値のクラスと予測値のクラスの分布が互いに独立であるとした場合に、混同行列の各セル$(i,j)$に属するレコード数の期待値。「真の値が$i$である割合x予測値が$j$である割合xデータ全体のレコード数」
- $w_{i, j}$: 真の値と予測値の差の二乗$(i-j)^2$。真の値に対して大きく離れたクラスを予測してしまうと、この値は二乗で大きくなるので、大きく予測を外してしまう場合に大きなペナルティが課せられる。

```math
\kappa=1-\frac{\sum_{i, j}w_{i, j}O_{i, j}}{\sum_{i, j}w_{i, j}E_{i, j}}
```

ポイント
- マルチクラス分類でクラス間に順序関係があるような場合に使用される（ex. 映画の評価。1~5のレーティング）
- 予測値として、各レコードがどのクラスに属しているかを提出
- 完全な予測で1、ランダムな予測で0、ランダムよりも悪い予測で負の値になる
- 予測値の各クラスの割合によって分母の値が変わるため、予測値を変えたときの動きが理解しづらい。予測値の各クラスの割合を固定したものとして考えると、二乗誤差に近い指標となり、少し理解しやすくなる

```python:計算例
from sklearn.metrics import confusion_matrix, cohen_kappa_score


def quadratic_weighted_kappa(c_matrix):
  numer = 0.0
  denom = 0.0

  for i in range(c_matrix.shape[0]):
    for j in range(c_matrix.shape[1]):
      n = c_matrix.shape[0]
      wij = ((i - j) ** 2.0)
      oij = c_matrix[i, j]
      eij = c_matrix[i, :].sum() * c_matrix[:, j].sum() / c_matrix.sum()
      numer += wij * oij
      denom += wij * eij
  
  return 1.0 - numer / denom


y_true = [1, 2, 3, 4, 3]
y_pred = [2, 2, 4, 4, 5]

c_matrix = confusion_matrix(y_true, y_pred, labels=[1, 2, 3, 4, 5])
kappa = quadratic_weighted_kappa(c_matrix)
print(kappa)
# 0.6153846153846154

# sklearnを使った例
kappa = cohen_kappa_score(y_true, y_pred, weights='quadratic')
print(kappa)
# 0.6153846153846154
```

コンペ例
- [Prudential Life Insurance Assessment](https://www.kaggle.com/c/prudential-life-insurance-assessment)

## レコメンデーションにおける評価指標

### MAP@K（Mean Average Precision @ K）

計算方法：
下記の数式は以下の表記に従う
- $m_i$: レコード$i$の属しているクラスの数
- $P_i(k)$: レコード$i$について、$k(1 \le k \le K)$番目までの予測値で計算される`precision`。ただし、k番目の予測値が正解である場合のみ値を取り、それ以外は0になる

```math
MAP@K=\frac{1}{N}\sum_{i=1}^N \left(\frac{1}{min(m_i,K)}\sum_{k=1}^KP_i(k) \right)
```

ポイント：
- 各レコードが一つまたは複数のクラスに属しているときに、属している可能性が高いと予測する順にK個のクラスを予測値とする
- Kの値は5や10など様々
- 各レコードについてK個未満の予測を行うことはできるが、そのようにすることでスコアが上がることはないので、通常はK個を予測値とする
- K個の中での正解数が同じでも、正解である予測値が後ろになってしまうとスコアが下がる。予測値の順序が重要
- 完全な予測で1、全く誤っている予測で0になる

```python:計算例
import numpy as np

# K=3, N=5, クラスは4種類
K = 3

y_true = [[1, 2], [1, 2], [4], [1, 2, 3, 4], [3, 4]]
# K=3より、通常は3個まで順位をつけ予測
y_pred = [[1, 2, 4], [4, 1, 2], [1, 4, 3], [1, 2, 3], [1, 2, 4]]


# average precision @ k の関数
def apk(y_i_true, y_i_pred):
  # y_predがK以下の長さで、要素が全て異なるならば処理。さもなければ例外を投げて止まる
  assert (len(y_i_pred) <= K)
  assert (len(np.unique(y_i_pred)) == len(y_i_pred))

  sum_precision = 0.0
  num_hits = 0.0

  for i, p in enumerate(y_i_pred):
    if p in y_i_true:
      num_hits += 1
      precision = num_hits / (i + 1)
      sum_precision += precision

  return sum_precision / min(len(y_i_true), K)


# MAP@Kの関数
def mapk(y_true, y_pred):
  return np.mean([apk(y_i_true, y_i_pred) for y_i_true, y_i_pred in zip(y_true, y_pred)])


print(mapk(y_true, y_pred))
# 0.6499999999999999

# 正解数が同じでも順序が違うとスコアが変わる
print(apk(y_true[0], y_pred[0]))
print(apk(y_true[1], y_pred[1]))
# 1.0
# 0.5833333333333333
```

コンペ例
- [Santander Product Recommendation](https://www.kaggle.com/c/santander-product-recommendation)

# 資料
豊富な解説がわかりやすく、ものすごく勉強になる資料です！

- [Kaggleで勝つデータ分析の技術](https://www.amazon.co.jp/Kaggle%E3%81%A7%E5%8B%9D%E3%81%A4%E3%83%87%E3%83%BC%E3%82%BF%E5%88%86%E6%9E%90%E3%81%AE%E6%8A%80%E8%A1%93-%E9%96%80%E8%84%87-%E5%A4%A7%E8%BC%94/dp/4297108437)
- [サンプルコード](https://github.com/ghmagazine/kagglebook)