Closed9

ranxを試す

kun432kun432

https://github.com/AmenRa/ranx

(日本語訳)

ranx([raŋks])は、高速なベクトル操作と自動並列化のためのNumbaを活用したPythonで実装された高速なランキング評価メトリックスのライブラリです。情報検索や推薦システムの評価・比較に便利なユーザーインターフェースを提供します。ranxでは、統計テストを行い、科学的な出版物のためのLaTeXテーブルをエクスポートすることができます。さらに、ranxはいくつかの融合アルゴリズムや正規化戦略を提供し、自動融合最適化機能も備えています。ranxには、モデル比較を容易にするために事前計算されたランを提供するコンパニオンリポジトリ、ranxhubもあります。ranxhubでは、MSMARCO Passage Rankingなどの情報検索データセットに対する事前計算済みランをダウンロードおよび共有することができます。ranxは、ECIR 2022、CIKM 2022、SIGIR 2023で取り上げられました。

科学的な出版物の結果を評価するためにranxを使用する場合、または融合に関連する実験を行う場合は、引用を考慮してください:評価bibtex、融合bibtex、ranxhub bibtex。

注:ranxは分類器の評価には適していません。詳細についてはFAQを参照してください。

概要を素早く確認するには、使用セクションをフォローしてください。

詳細な概要については、例セクションをフォローしてください。

ランキング評価メトリックスのライブラリね。

日本語で解説してくれているありがたい記事
https://hironsan.hatenablog.com/entry/2023/10/13/085841

kun432kun432

用語がまずわからないのでChatGPTに聞いてみた。

Qrel

Qrel(Quality Relevance)は、情報検索システムの評価において使われる用語です。これは、特定の検索クエリに対する文書の関連性を評価するための基準を提供します。一般的に、Qrelは「クエリID」と「文書ID」のペアで構成され、各ペアにはその文書がそのクエリに対してどれだけ関連があるかを示すスコアまたはラベルが割り当てられます。

情報検索システムの性能を評価する際、Qrelは基準となる真実(ground truth)を提供します。システムが生成した結果がQrelで指定された関連文書とどれだけ一致するかを分析することで、システムの精度や効率などを評価することができます。

たとえば、情報検索の研究やコンペティションでは、一連のクエリとそれに対応するQrelが提供され、参加者はそのクエリに最も関連する文書を見つけるシステムを開発します。そして、システムの出力されたランキングがQrelにどれだけ適合しているかを基に、そのシステムの性能が評価されます。

Run

情報検索の文脈において、「Run」という用語は、特定の検索システムやアルゴリズムによって生成された結果のセットを指します。具体的には、検索クエリに対する文書のリストやランキングで、そのシステムが関連性が高いと判断した文書が含まれます。

たとえば、情報検索の研究やコンペティションでは、各参加者は一連のクエリに対して自分たちのシステムを実行し、それぞれのクエリに対する最も関連性が高いと思われる文書のランキング(Run)を生成します。その後、これらのRunはQrel(Quality Relevance)と比較され、システムの精度や関連性を評価するために使われます。

また、Runは異なるアプローチやアルゴリズム、パラメータの設定によっても変わるため、同じ検索システムでも異なるRunが生成されることがあります。このようにして、研究者や開発者は様々なアプローチを試し、最も効果的な検索戦略を見つけ出すことができます。

Qrel=評価用(正解)データ、Run=結果データと考えればよさそう

kun432kun432

流れをさくっと追うために、まずは用意されているnotebookを実行してみる。

https://github.com/AmenRa/ranx/tree/master/notebooks

とりあえず1_overviewから

https://colab.research.google.com/github/AmenRa/ranx/blob/master/notebooks/1_overview.ipynb

インストール

!pip install ranx

テスト用データセットを取得

import os
import requests

for file in ["qrels", "run_1", "run_2", "run_3", "run_4", "run_5"]:
    os.makedirs("notebooks/data", exist_ok=True)

    with open(f"notebooks/data/{file}.trec", "w") as f:
        master = f"https://raw.githubusercontent.com/AmenRa/ranx/master/notebooks/data/{file}.trec"
        f.write(requests.get(master).text)

正解データと結果データx5を取得している様子。中身を見てみたけど、正解データはこんな感じ。

A35D452UPESD7P_259 0 B000S5Q9CA 1 
A2IPVT2ZRN0VX1_640 0 B004R9OVEG 1 
A3VEGTSHTWDZNI_630 0 B0079UAT0A 1 
A1X71ZRAH72HEH_155 0 B005DOK8NW 1 
A2OZ6PW2W5FQTP_520 0 B0094CX5M0 1 
A23KW9OO7EJC5H_585 0 B006GWO5WK 1 
(snip)

結果データ。何かしらデータをもとにスコアリングされたものって感じに見える。

A35D452UPESD7P_259 Q0 B001IBMLKM 1 0.5820152759552002 model_1
A35D452UPESD7P_259 Q0 B00CGYCOR0 2 0.5309091210365295 model_1
A35D452UPESD7P_259 Q0 B0018NWQPK 3 0.526623547077179 model_1
A35D452UPESD7P_259 Q0 B0014KOB6Y 4 0.44498544931411743 model_1
A35D452UPESD7P_259 Q0 B003NQ2GSW 5 0.4303796887397766 model_1
(snip)

ま、気にせず進める。

ライブラリインポート

from ranx import Qrels, Run, evaluate, compare

QrelとRunはpythonの辞書から作成することもできるらしい。

# The standard way of creating Qrels and Run is converting Python Dictionaries
qrels_dict = {"q_1": {"d_12": 5, "d_25": 3}, "q_2": {"d_11": 6, "d_22": 1}}

run_dict = {
    "q_1": {
        "d_12": 0.9,
        "d_23": 0.8,
        "d_25": 0.7,
        "d_36": 0.6,
        "d_32": 0.5,
        "d_35": 0.4,
    },
    "q_2": {
        "d_12": 0.9,
        "d_11": 0.8,
        "d_25": 0.7,
        "d_36": 0.6,
        "d_22": 0.5,
        "d_35": 0.4,
    },
}

qrels = Qrels(qrels_dict)
run = Run(run_dict)

QrelとRunを、評価指標とともにevaluateに渡して、評価する。複数指標を渡すこともできる。

# Compute NDCG@5
score = evaluate(qrels, run, "ndcg@5")
print(score)

# Compute NDCG@3, MAP@5, and MRR
score_dict = evaluate(qrels, run, ["ndcg@3", "map@5", "mrr"])
print(score_dict)

結果

/usr/local/lib/python3.10/dist-packages/ranx/metrics/ndcg.py:72: NumbaTypeSafetyWarning: unsafe cast from uint64 to int64. Precision may be lost.
  scores[i] = _ndcg(qrels[i], run[i], k, rel_lvl, jarvelin)
0.7861261099276952
{'ndcg@3': 0.7569557807573265, 'map@5': 0.6416666666666666, 'mrr': 0.75}

評価用の指標は以下。

  • Hits
  • Hit Rate
  • Precision
  • Recall
  • F1
  • r-Precision
  • Bpref
  • Rank-biased Precision (RBP)
  • Mean Reciprocal Rank (MRR)
  • Mean Average Precision (MAP)
  • Discounted Cumulative Gain (DCG)
  • Normalized Discounted Cumulative Gain (NDCG)

知らんので聞いてみた。

  1. Hits(ヒット数)
  • ある問い(クエリ)に対して、関連する答え(文書や商品など)がいくつ見つかったかを数える指標です。例えば、5つの関連する答えがある中で、3つを見つけた場合、ヒット数は3です。
  1. Hit Rate(ヒット率)
  • ヒット数を全体の問いの数で割ったものです。たとえば、10の問いのうち、3つで関連する答えを見つけた場合、ヒット率は3割(30%)になります。
  1. Precision(精度)
  • 提示された答えの中で、実際に関連しているものがどれだけあるかを示します。例えば、10個の答えのうち5個が正しい場合、精度は50%です。
  1. Recall(再現率)
  • 全ての関連する答えの中で、どれだけ見つけられたかを示します。例えば、全10個の関連する答えのうち5個を見つけた場合、再現率は50%です。
  1. F1(F1スコア)
  • 精度と再現率のバランスを示す指標です。両者が高いほど良いスコアになります。
  1. r-Precision(r精度)
  • 関連する答えの総数がr個の場合、上位r個の答えの中に関連するものがどれだけあるかを示します。
  1. Bpref(Bprefスコア)
  • 関連する答えと関連しない答えがどの程度正しい順番で並んでいるかを示します。
  1. Rank-biased Precision (RBP)
  • ユーザーがリストの上の方の答えにもっと注目することを考慮した指標です。上位の答えほど重要とみなします。
  1. Mean Reciprocal Rank (MRR)
  • 最初の正解が見つかるまでの平均的な順位を示します。より早く正解が見つかるほど良いスコアになります。
  1. Mean Average Precision (MAP)
  • 全ての問いに対する平均精度の平均を示します。それぞれの問いについて、正しい答えが見つかるたびに計算された精度の平均を取ります。
  1. Discounted Cumulative Gain (DCG)
  • 関連する答えがリストの上位にあるほど高い得点を与える指標です。関連性の高い答えほど高得点になります。
  1. Normalized Discounted Cumulative Gain (NDCG)
  • DCGを正規化したもので、理想的な順番(最良の場合)と比較して、どれだけ良い順番で答えが並んでいるかを示します。

これらの指標は、検索システムや推薦システムがどれだけ良い結果を提供しているかを評価するのに役立ちます。

評価指標を指定したあとの@数字はランキング上位何件を評価対象にするか。「カットオフ」というらしい。

「NDCG@5」のように、NDCG(Normalized Discounted Cumulative Gain)の後に「@数字」とある場合、それはランキングの上位何件を評価対象にするかを指定しています。この「@」の後の数字は「カットオフ」と呼ばれ、ランキングのどこまでを考慮するかを示しています。

たとえば、「NDCG@5」は、ランキングの上位5件のみを考慮してNDCGを計算することを意味します。これは、ユーザーが通常、検索結果の最初の数件しか見ないという現実を反映しています。つまり、最初の5件の中でどれだけ関連性の高い結果が得られているかが重要になります。

同様に、「NDCG@10」は、上位10件を評価の対象とします。このようにカットオフを設定することで、ランキングの特定の範囲に焦点を当てて評価を行うことができます。これは、全体のランキングを評価するよりも実用的な場合が多いです。なぜなら、多くの場合、ユーザーはランキングの上位の結果に最も関心を持っているからです。

ということで、最初のサンプルデータでやってみる。

正解データと結果データをロード。

# Let's load qrels and runs from files and compare them
qrels = Qrels.from_file("notebooks/data/qrels.trec", kind="trec")

run_1 = Run.from_file("notebooks/data/run_1.trec", kind="trec")
run_2 = Run.from_file("notebooks/data/run_2.trec", kind="trec")
run_3 = Run.from_file("notebooks/data/run_3.trec", kind="trec")
run_4 = Run.from_file("notebooks/data/run_4.trec", kind="trec")
run_5 = Run.from_file("notebooks/data/run_5.trec", kind="trec")

評価

# Compares different runs and performs statistical tests (Fisher's Randomization test)
report = compare(
    qrels,
    runs=[run_1, run_2, run_3, run_4, run_5],
    metrics=["map@100", "mrr@100", "ndcg@10"],
    max_p=0.01,  # P-value threshold
)

# The comparison results are saved in a Report instance,
# which provides handy functionalities such as tabular formatting
# (superscripts denote statistical significance differences)
report

結果

#    Model    MAP@100    MRR@100    NDCG@10
---  -------  ---------  ---------  ---------
a    model_1  0.320ᵇ     0.321ᵇ     0.368ᵇᶜ
b    model_2  0.233      0.234      0.239
c    model_3  0.308ᵇ     0.309ᵇ     0.329ᵇ
d    model_4  0.366ᵃᵇᶜ   0.367ᵃᵇᶜ   0.408ᵃᵇᶜ
e    model_5  0.405ᵃᵇᶜᵈ  0.406ᵃᵇᶜᵈ  0.451ᵃᵇᶜᵈ

結果をLaTeXで出力できる。

print(report.to_latex())
========================
% Add in preamble
\usepackage{graphicx}
\usepackage{booktabs}
========================


% To change the table size, act on the resizebox argument `0.8`.
\begin{table*}[ht]
\centering
\caption{
Overall effectiveness of the models.
The best results are highlighted in boldface.
Superscripts denote significant differences in paired Student's t-test with $p \le 0.01$.
}
\resizebox{0.8\textwidth}{!}{
\begin{tabular}{c|l|c|c|c}
\toprule
\textbf{\#}
& \textbf{Model}
& \textbf{MAP@100}
& \textbf{MRR@100}
& \textbf{NDCG@10} \\ 
\midrule
a &
model\_1 &
0.320$^{b}$\hphantom{$^{cde}$} &
0.321$^{b}$\hphantom{$^{cde}$} &
0.368$^{bc}$\hphantom{$^{de}$} \\
b &
model\_2 &
0.233\hphantom{$^{acde}$} &
0.234\hphantom{$^{acde}$} &
0.239\hphantom{$^{acde}$} \\
c &
model\_3 &
0.308$^{b}$\hphantom{$^{ade}$} &
0.309$^{b}$\hphantom{$^{ade}$} &
0.329$^{b}$\hphantom{$^{ade}$} \\
d &
model\_4 &
0.366$^{abc}$\hphantom{$^{e}$} &
0.367$^{abc}$\hphantom{$^{e}$} &
0.408$^{abc}$\hphantom{$^{e}$} \\
e &
model\_5 &
\textbf{0.405}$^{abcd}$\hphantom{} &
\textbf{0.406}$^{abcd}$\hphantom{} &
\textbf{0.451}$^{abcd}$\hphantom{} \\
\bottomrule
\end{tabular}
}
\label{tab:results}
\end{table*}
kun432kun432

この分野における基本的な知識が足りなさすぎるなぁ・・・と感じつつも進めていく。ここからはメモだけ。

https://github.com/AmenRa/ranx/blob/master/notebooks/2_qrels_and_run.ipynb

pandasのデータフレームからqrelsを作成する例

from pandas import DataFrame

qrels_df = DataFrame.from_dict(
    {
        "q_id": ["q_1", "q_1", "q_2", "q_2"],
        "doc_id": ["d_12", "d_25", "d_11", "d_22"],
        "score": [5, 3, 6, 1],
    }
)

qrels = Qrels.from_df(
    df=qrels_df,
    q_id_col="q_id",
    doc_id_col="doc_id",
    score_col="score",
)

print(qrels)

ふーむ、クエリID、ドキュメントID、スコアってのがQrelのフォーマットなのかな。サンプルデータにあった.trecってのはこういう形式らしい。

.trec拡張子を持つファイルは、一般的にTREC(Text Retrieval Conference)関連のデータセットで使用される形式です。TRECは、情報検索システムの研究と開発を促進するための一連のワークショップで、多くの異なる検索タスクやチャレンジを提供しています。

.trec形式のファイルは、特にqrels(Quality Relevance Judgments)の文脈で言及されることが多いです。qrelsファイルは、情報検索の評価に使われ、特定のクエリに対してどの文書が関連しているか(そしてどの程度関連しているか)を示します。これらのファイルは、情報検索システムが生成した結果を評価するための「基準」として機能します。

.trec形式のqrelsファイルは通常、以下のような形式で記述されます:

  • クエリID
  • 文書ID
  • 関連性のラベル(通常は数値で、高いほどより関連があると判断されます)

これらのファイルは、情報検索システムの性能評価に不可欠であり、特にTRECのような研究コンテキストで頻繁に使用されます。TRECは情報検索の分野で広く尊敬されており、そのデータセットは多くの研究やシステム開発で基準として用いられています。

んー、サンプルのデータと合わないな。

A35D452UPESD7P_259 0 B000S5Q9CA 1 

この真ん中の0はなんだろう?

qrels = Qrels.from_file("notebooks/data/qrels.trec", kind="trec")
print(qrels["A35D452UPESD7P_259"])
{'B000S5Q9CA': 1}

Runのフォーマットもよくわからない。

A35D452UPESD7P_259 Q0 B001IBMLKM 1 0.5820152759552002 model_1
A35D452UPESD7P_259 Q0 B00CGYCOR0 2 0.5309091210365295 model_1
A35D452UPESD7P_259 Q0 B0018NWQPK 3 0.526623547077179 model_1
A35D452UPESD7P_259 Q0 B0014KOB6Y 4 0.44498544931411743 model_1
A35D452UPESD7P_259 Q0 B003NQ2GSW 5 0.4303796887397766 model_1
(snip)
run_1 = Run.from_file("notebooks/data/run_1.trec", kind="trec")
pprint(run_1["A35D452UPESD7P_259"])
{'B00001W0EQ': 0.06656122207641602,
 'B00007M1TZ': 0.18607020378112793,
 'B00024ENES': 0.03088870644569397,
 'B0009IIE5G': 0.06731614470481873,
 'B000ANOY7M': 0.09240823984146118,
 'B000EDIKVO': 0.050716131925582886,
(snip)

辞書からQrelを作成して、.trec/.jsonで出力してみる。

from pandas import DataFrame

qrels_df = DataFrame.from_dict(
    {
        "q_id": ["q_1", "q_1", "q_2", "q_2"],
        "doc_id": ["d_12", "d_25", "d_11", "d_22"],
        "score": [5, 3, 6, 1],
    }
)

qrels = Qrels.from_df(
    df=qrels_df,
    q_id_col="q_id",
    doc_id_col="doc_id",
    score_col="score",
)

qrels.save("small_qrels.json")
qrels.save("small_qrels.trec")

json

{
  "q_1": {
    "d_12": 5,
    "d_25": 3
  },
  "q_2": {
    "d_11": 6,
    "d_22": 1
  }
}

trec。0入ってるので何してんのかなと思ってコード見てみたら、ハードコードで0を入れてた。

q_1 0 d_12 5
q_1 0 d_25 3
q_2 0 d_11 6
q_2 0 d_22 1

qrelとrunのフォーマットはここで定義されている。

https://github.com/AmenRa/ranx/blob/master/ranx/data_structures/qrels.py

https://github.com/AmenRa/ranx/blob/master/ranx/data_structures/run.py

んー、やっぱりよくわからない。出力して使い回すならJSONとか汎用性のあるフォーマットを使うほうがいい気がする。ranxでしか使わないならtrecでもいいと思う。というかQrelもRunもranxで定義されているクラスでaddとかのメソッドがあるようだし、検索結果をそのままぶち込んで、どうしても残しておきたいとかのケースだけJSONで持っておく、というのがいいと思った。フォーマットを気にする必要は余りなさそう。

kun432kun432

https://colab.research.google.com/github/AmenRa/ranx/blob/master/notebooks/5_fusion.ipynb

rank fusion=複数の結果を統合して一つのランキングにするってことね。ハイブリッド検索でreciprocal rank fusionとかちょっとやったな。

対応しているのは以下。ChatGPTにも聞いてみた。

アルゴリズム エイリアス 特徴 メリット デメリット ユースケース 最適化フェーズ
CombMIN min 最低スコアの合計を使用 異なるランキングの最悪のケースを考慮 最良のランキングが無視される可能性 統一的な最低基準を設定したい場合 不要
CombMAX max 最高スコアの合計を使用 最良のランキングに焦点 最悪のランキングが無視される可能性 最高の成績を重視する場合 不要
CombMED med 中央値の合計を使用 個々の極端な値の影響を受けにくい 平均的な結果に偏る可能性 バランスの取れた評価を求める場合 不要
CombSUM sum スコアの合計を使用 簡単で直感的 スコアの尺度に依存 全体的な一貫性を求める場合 不要
CombANZ anz 平均スコアを使用 各ランキングの寄与を平均化 少数の高スコアに偏る可能性 個々のランキングの平均を重視する場合 不要
CombMNZ mnz スコアの合計に文書数を掛ける 頻繁にランクされる文書を重視 特定の文書に偏る可能性 頻繁にランクされる文書を重視する場合 不要
CombGMNZ gmnz ジオメトリック平均を使用 外れ値の影響を受けにくい 計算が複雑 外れ値の影響を避けたい場合 不要
ISR isr 逆スコアランキング 個々のスコアに基づく スコアの尺度に依存 細かいスコア差を重視する場合 不要
Log_ISR log_isr ログ逆スコアランキング 大きなスコアの影響を減少 スコアが0に近い場合問題 スコアの大きな差を緩和したい場合 不要
LogN_ISR logn_isr 正規化ログ逆スコアランキング スケールの違いを調整 正規化による情報の損失 異なる尺度のスコアを統合したい場合 必要
Reciprocal Rank Fusion (RRF) rrf 逆ランク融合 直感的で効果的 順位のみに依存 順位のみを重視する場合 必要
PosFuse posfuse 位置情報に基づく融合 位置の重要性を反映 位置の偏りに影響される 位置に基づく優先順位を設定したい場合 必要
ProbFuse probfuse 確率的融合 確率モデルに基づく 確率モデルの精度に依存 確率的アプローチを採用したい場合 必要
SegFuse segfuse セグメントに基づく融合 セグメントの特性を利用 セグメント分割の方法に依存 セグメントに基づく分析を行いたい場合 必要
SlideFuse slidefuse スライド窓に基づく融合 時間的側面を考慮 スライド窓のサイズに依存 時間的変動を考慮したい場合 必要
MAPFuse mapfuse 平均精度に基づく融合 精度を重視 計算が複雑 精度を最優先にしたい場合 必要
BordaFuse bordafuse ボルダカウントに基づく融合 公平でバランスが取れる 個々のランキングの特性を無視 総合的な順位付けを行いたい場合 不要
Weighted BordaFuse w_bordafuse 重み付きボルダカウント カスタマイズ可能 重み付けの選択に依存 カスタマイズされた重み付けが必要な場合 必要
Condorcet condorcet コンドルセ法に基づく融合 各ペアの比較に基づく 計算が複雑 総合的な比較が重要な場合 不要
Weighted Condorcet w_condorcet 重み付きコンドルセ法 カスタマイズ可能 重み付けの選択に依存 カスタマイズされた比較が必要な場合 必要
BayesFuse bayesfuse ベイジアンモデルに基づく融合 統計的アプローチ モデルの複雑さ 統計的手法を適用したい場合 必要
Mixed mixed 複数の方法を混合 柔軟性が高い 組み合わせの選択に依存 カスタマイズされた組み合わせが必要な場合 必要
WMNZ wmnz 重み付きCombMNZ カスタマイズ可能 重み付けの選択に依存 個別のランキングの重み付けが必要な場合 必要
Weighted Sum wsum 重み付き合計スコア カスタマイズ可能 重み付けの選択に依存 スコアの合計に基づく分析を行いたい場合 必要
Rank-Biased Centroids (RBC) rbc ランクバイアス中心に基づく融合 ユーザビヘイビアを反映 パラメータの選択に依存 ユーザの行動を考慮したい場合 必要

これらのアルゴリズムは、異なる情報源からのランキングを効果的に統合し、情報検索や推薦システムなどで使用されます。それぞれの手法は特定のシナリオや要件に応じて選択され、ランク融合の精度と有用性を高めます。

統合するにはfuseを使う。methodに上記のランキングアルゴリズム、normに後述する正規化手法を指定して、複数の結果データを渡す。

from ranx import fuse, evaluate

print(run_4.name, evaluate(qrels, run_4, "ndcg@100"))
print(run_5.name, evaluate(qrels, run_5, "ndcg@100"))

for method in [
    "min",  # Alias for CombMIN
    "max",  # Alias for CombMAX
    "med",  # Alias for CombMED
    "sum",  # Alias for CombSUM
    "anz",  # Alias for CombANZ
    "mnz",  # Alias for CombMNZ
]:
    combined_run = fuse(
        runs=[run_4, run_5],
        norm="min-max",  # Default normalization strategy
        method=method,
    )

    print(combined_run.name, evaluate(qrels, combined_run, "ndcg@100"))

結果

/usr/local/lib/python3.10/dist-packages/ranx/metrics/ndcg.py:72: NumbaTypeSafetyWarning: unsafe cast from uint64 to int64. Precision may be lost.
  scores[i] = _ndcg(qrels[i], run[i], k, rel_lvl, jarvelin)
System A 0.45236291280341645
System B 0.501471970057649
comb_min 0.45254781751341444
comb_max 0.5292500580609943
comb_med 0.487777487717754
comb_sum 0.5454908087414865
comb_anz 0.487777487717754
comb_mnz 0.532500382206197

正規化と最適化について

ランク融合(Rank Fusion)における「正規化(Normalization)」と「最適化(Optimization)」は、複数のランキングリストを効果的に統合するための重要な手順です。これらは異なるランキングリストを比較可能にし、結果の統合ランキングの質を向上させるために用いられます。

  1. 正規化(Normalization)

    • 正規化の目的は、異なるランキングリストを同じ尺度に合わせることです。ランキングリストは異なる情報源やアルゴリズムから来ており、そのスコアや順位の尺度が異なることがあります。正規化によって、これらのリストを共通の尺度に変換し、比較可能にします。
    • 例えば、一つのランキングが0から1のスコアを使用している一方で、別のランキングが0から100のスコアを使用している場合、これらのスコアを同じ範囲に正規化することで、一貫した比較が可能になります。
  2. 最適化(Optimization)

    • 最適化は、統合されたランキングの質を最大化するために行われます。これには、異なるランキングリストからの情報をどのように重み付けし、組み合わせるかという決定が含まれます。
    • 最適化では、しばしば特定のメトリクス(例えば、精度、再現率、NDCGなど)を最大化するためにパラメータを調整します。これには、機械学習の技術を利用して、最も効果的な重み付けや組み合わせ方を見つけることが含まれることがあります。
    • 最適化は、異なるランキングリストが持つ情報の相対的な価値を考慮し、最終的な統合ランキングが最も関連性の高い結果を提供するようにします。

正規化と最適化は、ランク融合プロセスの重要な部分であり、複数のランキングリストを効果的に組み合わせて、より高品質な結果を提供するために必要です。これらの手順によって、異なる情報源やアルゴリズムの強みを活用し、最終的なランキングリストの有用性と正確性を高めることができます。

さっきのコードだとここ

    combined_run = fuse(
        runs=[run_4, run_5],
        norm="min-max",  # デフォルトの正規化手法
        method=method,
    )

正規化は以下から選択できる様子

  • max_norm
  • min_max_norm
  • rank_norm
  • sum_norm
  • zmuv_norm
  • borda_norm

これらはランク融合のための正規化手法で、異なるランキングを統一的な尺度に変換する際に使用されます。それぞれの手法の正式名称、説明、最適なユースケース、メリットとデメリットを以下に示します。

  1. Max Normalization (最大値正規化) - max_norm

    • 説明: 各ランキングのスコアをそのランキングの最大スコアで割ることで正規化します。
    • ユースケース: 最大値に基づく比較が重要な場合に適しています。
    • メリット: 最大値を基準にすることで、ランキング間のスコアを直接比較できます。
    • デメリット: 最大値が異常に高いランキングは過大評価される可能性があります。
  2. Min-Max Normalization (最小-最大正規化) - min_max_norm

    • 説明: スコアを最小値と最大値の間で正規化します(通常は0から1の範囲)。
    • ユースケース: 異なる尺度のランキングを同一の範囲で比較したい場合に適しています。
    • メリット: すべてのランキングが同じスケールで比較可能になります。
    • デメリット: 個々のランキングのスコア分布の違いが無視される可能性があります。
  3. Rank Normalization (順位正規化) - rank_norm

    • 説明: 各文書の順位に基づいて正規化します。
    • ユースケース: 順位に基づく比較が重要な場合に適しています。
    • メリット: 異なるランキングの順位を直接比較できます。
    • デメリット: 順位のみに基づくため、スコアの絶対値の違いを考慮できません。
  4. Sum Normalization (合計値正規化) - sum_norm

    • 説明: 各ランキングのスコアの合計を1になるように正規化します。
    • ユースケース: スコアの相対的な比重を保持しながらランキングを統合したい場合に適しています。
    • メリット: 全体の合計値に基づくため、バランスの取れた比較が可能です。
    • デメリット: 個々のランキングのスコアの影響を受けやすいです。
  5. Z-score Mean and Variance Unit Normalization (Zスコア平均・分散単位正規化) - zmuv_norm

    • 説明: 各ランキングのスコアから平均を引き、標準偏差で割って正規化します。
    • ユースケース: 異なるランキングのスコア分布を統一的に扱いたい場合に適しています。
    • メリット: スコアの分布を考慮した比較が可能です。
    • デメリット: 極端な値に敏感で、それらの影響を受けやすいです。
  6. Borda Count Normalization (ボルダカウント正規化) - borda_norm

    • 説明: ボルダカウント方式に基づいてランキングを正規化します。これは

、順位に応じて点数を割り当て、合計します。

  • ユースケース: 総合的な順位付けを行いたい場合に適しています。
  • メリット: 順位ごとに一定の重みを与えることで、公平な比較が可能です。
  • デメリット: 各ランキングの特性や文書数の違いが考慮されない場合があります。

これらの正規化手法は、異なるランキングリスト間での公平な比較と統合を可能にし、ランク融合プロセスをサポートします。適切な手法の選択は、使用するランキングリストの特性や統合の目的によって異なります。

次に最適化。統合のアルゴリズムの中には最適化が必要になるものがある。その場合には、optimize_fusionを使って最適なパラメータを推定させて、fuseのparamsで指定する。これはちょっと時間がかかる。

from ranx import fuse, evaluate, optimize_fusion

print(run_4.name, evaluate(qrels, run_4, "ndcg@100"))
print(run_5.name, evaluate(qrels, run_5, "ndcg@100"))

# Optimize a given fusion method
best_params = optimize_fusion(
    qrels=qrels,
    runs=[run_4, run_5],
    norm="min-max",  # Default value
    method="wsum",  # Alias for Weighted Sum
    metric="ndcg@100",  # Metric we want to maximize
)

combined_run = fuse(
    runs=[run_4, run_5],
    norm="min-max",  # Default value
    method="wsum",  # Alias for Weighted Sum
    params=best_params,
)

print(combined_run.name, evaluate(qrels, combined_run, "ndcg@100"))
System A 0.45236291280341645
System B 0.501471970057649
Optimizing WSUM ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:35
weighted_sum 0.555987399857977
kun432kun432

ざっくり使い方がわかったので、実際のRAG検索で少し試してみたいと思う。

kun432kun432

trectoolsにあるQrelのフォーマットを見ていて、よくわからないのが、relavance scoreの範囲

1 0 aldf.1864_12_000027 1
1 0 aller1867_12_000032 2
1 0 aller1868_12_000012 0
1 0 aller1871_12_000640 1
1 0 arthr0949_12_000945 0
1 0 arthr0949_12_000974 1

TRECの公式を見る限りは以下とある。

qrels ファイル内のドキュメントの順序は、関連性や関連性の程度を示すものではありません。**関連 (1) または非関連 (0) のバイナリ表示のみが与えられる。qrels ファイルに存在しない文書は人間の評価者によって判断されず、TREC で使用される評価では無関係であると見なされる。**人間の評価者は、文書のどの部分でも関連性があれば(その部分が文書の残りの部分に対してどれだけ小さいかにかかわらず)関連性があると判断するように指示される。

関連性のあるものに1を付与すればいいのはわかった。では、2というのは何を指すのか?ranxのサンプルを見ていても、0 or 1じゃない数値が当てられている場合がある。

https://amenra.github.io/ranx/qrels/

ranxのissueにも質問がある。

https://github.com/AmenRa/ranx/issues/58

qrelsのスコアは正負を問わない任意の整数である。
bpref以外のすべての実装されたメトリクスは、関連性のあるドキュメントのみを考慮します。つまり、すべてのqrels < 1を破棄します。evaluateとcompareメソッドを使用する際に、メトリクス名に-ln(nはパラメータ)を追加することで、この閾値を変更することができます(例えば、recall-l2は、recallスコアを計算する際に、すべてのqrels < 2を非関連とみなすことを意味します)。
DCGとnDCGを除くすべての実装されたメトリクスは、関連性をバイナリとみなしている。

逆に言うと、DCGとnDCGは、0 or 1以外の数値を取りうるというように読める。

以下にも記載がある。

https://mofumofu1729.hatenablog.com/entry/2020/12/15/230213

平均適合率などでは,クエリと文章の関係は関係は関連か非関連のどちらかであると仮定しています. しかし,実際は関連と非関連の間にはグラデーションがあるはずです.

nDCG(normalized Discounted Cumulative Gain, 正規化された割引累積利得) は,段階的な関連度を考慮した指標です.

んー、Qrelの値が0/1以外の場合にどういう計算になるのかを確認する必要があるな。。。

このスクラップは6ヶ月前にクローズされました