糞糞糞ネット弁慶

読んだ論文についてメモを書きます.趣味の話は http://repose.hatenablog.com

ランダムな予測値における ROC-AUC は0.5,では nDCG は?

先に結論

nDCG@all はどんな予測値やモデルであっても 1.0 に近づくので注意したほうが良さそうです.

疑問

機械学習モデルにおける予測値の評価にはさまざまな指標が用いられます.

  • RMSE
  • prec / recall / f1-score
  • negative log-likelihood

二値分類ではとくに ROC-AUC (Area Under the Receiver Operating Characteristic Curve)1が用いられることが多いでしょう.

ランダムな予測値に対する ROC-AUC はその定義上からも 0.5 になることが知られています.これは非常に便利で,ROC-AUC の厳密な定義を知らない人でも「この予測結果はコイントスよりどれだけ優れているのか」がすぐに把握可能です. (ちなみに ROC-AUC については Quality Metrics in Recommender Systems: Do We Calculate Metrics Consistently? (RecSys 2021) に非常に興味深い話が複数掲載されているので,いつかまとめます.)

ところで,二値分類問題を「予測結果にもとづいて並び替えた時に,より真のラベルを持つものが先頭に来るように並んでいるか?」のようなランキング問題として捉えましょう. この場合は評価指標に nDCG (Normalized Discounted Cumulative Gain)2を用いることがしばしばあります(WWW20113 や KDD20194 など).

(ここでは nDCG の定義は割愛します) さて,機械学習モデルを構築し予測値を得て nDCG で評価する時,「ランダムな予測値に対して nDCG はどのような値を取るのだろうか?」と疑問に思うのが自然でしょう.

関連研究

これに取り組んでいるのが A Theoretical Analysis of NDCG Ranking Measures (COLT 2013) です (余談ですが 2024年2月4日現在,google 検索によるとこの論文に日本語でリンクしているのは 推薦システムの基本的な評価指標について整理してみた | NHN テコラス Tech Blog | AWS、機械学習、IoTなどの技術ブログ のみであり,とはいえ論文の内容には全く言及されていません).

この論文では 3.1 にて

Surprisingly, it is easy to show that for every ranking function, standard NDCG (引用者による注釈 : nDCG@all のこと) converges to 1 almost surely. (中略) At the first glance, the above result is quite negative for standard NDCG. It seems to say that in the limiting case, standard NDCG cannot differentiate ranking functions.

と書かれており,証明である Appendix E を読まずとも Appendix A を見ると

  • nDCG@all (評価に全件を用いる場合) はほぼ1に収束しているものの,どうにかアルゴリズム間で多少の差がある (Figure 1)
  • nDCG@k (評価にk件を用いる場合) はかなりよく区別がつけられている (Figure 4)

ことがわかります.

実験

COLT の論文の数式を追うのは疲れるので,python のコードで再現しましょう.大まかな方針です.

  • N = 100, 200, ..., 100000 と変化させながら以下を繰り返す
    • p を 0.1, 0.3, 0,5 で変化させながら以下を繰り返す
      • 以下を100回繰り返し,それぞれの nDCG の平均値を計算する
        • ランダムな値を N 個作り,予測値とする
        • N 個のうち,確率 p でランダムに 1 を,残りに 0 を振ってこれを正解ラベルとする
        • k = 1, 10, all で変化させ以下を繰り返す
          • nDCG@k(正解ラベル, 予測値, k) を評価する
from collections import defaultdict

import matplotlib.pyplot as plt
import numpy as np
from sklearn.metrics import ndcg_score
from tqdm import tqdm

# サンプルデータの生成
rng = np.random.default_rng(6162)

results = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: list())))

x_indices = []
top_ks = [1, 10, "all"]
probs = [0.1, 0.3, 0.5]
for n_samples in tqdm(range(1000, 100001, 1000), ascii=True):
    x_indices.append(n_samples)
    predicts = rng.random(n_samples)
    for p in probs:
        n_answers = int(n_samples * p)
        for _ in range(100):
            answers = np.zeros(n_samples, dtype=int)
            answers[:n_answers] = 1
            rng.shuffle(answers)
            for k in top_ks:
                _k = n_samples if k == "all" else k
                ndcg_at_k = ndcg_score([answers], [predicts], k=_k)
                results[p][k][n_samples].append(ndcg_at_k)

# show
for k in top_ks:
    labels = []
    values = []
    for p, h in results.items():
        _h = h[k]
        label = f"p={p}, ndcg@{k}"
        labels.append(label)
        vals = [np.average(_h[n]) for n in x_indices]
        values.append(vals)

    for value in values:
        plt.plot(x_indices, value)

    plt.legend(labels)
    plt.title(f"ndcg@{k}")

    plt.savefig(f"/tmp/hoge_{k}.png")
    plt.close()

グラフを見ると

nDCG@all
nDCG@1
nDCG@10

といったように

  • nDCG@all は正解ラベルの割合によらず,順調に右肩上がりで 1 に近づいている
  • nDCG@k は震えており,心の目で見ると k の値によらずに正解ラベルの割合 = p 周辺にいるように見える

がわかります.特に nDCG@all は見事に再現できました.

結論

  • nDCG@all は非常に大きな値が出てしまうこと,アルゴリズム間の値の差が小さく見えてしまうことから,利用時には十分注意したほうが安心でしょう
  • nDCG@k がランダムな予測でいくつになるのかはよくわかりません.心の目で見ると k の値によらずに正解ラベルの割合 = p に収束しているようにも見えます
  • COLT の論文が読める人がいたら教えてください

参考文献