CoolFace
Datasetpublic

mahiyama/quiz-works

mahiyama/quiz-works クイズワークス (https://quiz-works.com/) のクイズ Q&A を日本語版 Wikipedia パッセージで根拠付けした Open-domain QA データセットに、本リポ内で Hard Negative Mining と Cross-Encoder 蒸留スコア付与を実施した日本語 retrieval / 蒸留学習用データセット。 本リポに供給される (query, positive) ペアは、クイズワークス のクイズ問題文を query、 その短い正答 (例: 「青年海外協力隊」) を 日本語版 Wikipedia 全 passage コレクションへ 引き当てて取得した 根拠 passage 本文 を positive としたもの。短答テキストから passage 本文への引き当て (hydration) は別パイプラインで済んだ状態で入力されており、本リポでは それを起点に Hard Negative Mining と Cross-Encoder… See the full description on the dataset page: https://huggingface.co/datasets/mahiyama/quiz-works.

sourceHugging Facecc-by-sa-4.0updated 4mo agoView on Hugging Face
0likes38downloads
Dataset Card

mahiyama/quiz-works

クイズワークス (https://quiz-works.com/) のクイズ Q&A を日本語版 Wikipedia パッセージで根拠付けした Open-domain QA データセットに、本リポ内で Hard Negative Mining と Cross-Encoder 蒸留スコア付与を実施した日本語 retrieval / 蒸留学習用データセット。

本リポに供給される (query, positive) ペアは、クイズワークス のクイズ問題文を query、 その短い正答 (例: 「青年海外協力隊」) を 日本語版 Wikipedia 全 passage コレクションへ 引き当てて取得した 根拠 passage 本文 を positive としたもの。短答テキストから passage 本文への引き当て (hydration) は別パイプラインで済んだ状態で入力されており、本リポでは それを起点に Hard Negative Mining と Cross-Encoder 蒸留スコア付け、quality_score フィルタを行って pairs / triplets / n-tuples / n-tuples-filtered の 4 形式に 組み替えている。

Configs

configrowscolumns説明
pairs16,996query, positive元 (query, positive) ペアそのまま。フィルタなし。
triplets16,996query, positive, negative1 hard negative を付けた triplet。negative は n-tuples の negative_1 と同一 (positive-aware filter 通過の最高スコア 1 件)。フィルタなし。
n-tuples16,996query, positive, negative1〜negative5, label5 hard negatives + Cross-Encoder の生 logit を label に付与した全行。フィルタなし。
n-tuples-filtered12,502query, positive, negative1〜negative5, labeln-tuples のうち quality_score が valid (偽 negative / weak positive / borderline の 3 条件をすべてクリア) の行のみ。

すべて split は train。triplets / n-tuples は全行 (フィルタ未適用)、n-tuples-filtered は valid 行のみ。

label カラムの形式 (n-tuples / n-tuples-filtered 共通)

長さ 6 の List[float] で [score(query, positive), score(query, neg1), …, score(query, neg5)]。 label[0] が positive、label[1:] が negative1 〜 negative5 に 1:1 で順序通り対応する。 スコアは Cross-Encoder の生 logit (sigmoid 適用前)。 本データの観測 score の絶対値最大は約 12.59。

上流データの形式について

本リポに入ってくる (query, positive) ペアは「クイズ問題文 × 短答ベタ書き」ではなく、 「クイズ問題文 × その短答を Wikipedia 全 passage コレクションへ引き当てて hydrate した 根拠 passage 本文」になっている。具体例:

カラム中身 (短答 hydration 後)
query応募資格があるのは20歳から39歳までの男女。合格すると80日間の国内研修と現地での一ヵ月の語学研修を経て…国際協力事業団が運営する開発途上国の発展に協力する制度は何?
positive青年海外協力隊 応募資格は、日本国籍を持つ20歳以上の心身ともに健康な者… (Wikipedia 該当エントリの passage 本文)

短答テキスト (例: 「青年海外協力隊」) 自体は本リポには含まれない。短答が必要なユース ケースでは、原典 (クイズワークス) もしくは passage 引き当ての元になった public な オープン Wikipedia retrieval リソースから query (anchor) キーで join し直して 取得すること。

Hard Negative 選定の二段階フロー

各 (query, positive) 行について、negative1 〜 negative5 を以下の二段階で選出する。 2 段階の役割を分離することで、Stage 1 (dense retriever) で「意味的に近い候補を広く拾い」、 Stage 2 (cross-encoder) で「真に紛らわしい hard negative だけを残す」という構造になっている。

Stage 1: dense retriever による候補集めの粗探索 (top-15)

  • —cl-nagoya/ruri-v3-310m で query / 全ユニーク positive (15,313 件) を 768 次元にエンコードする (prompt = 「検索クエリ:」 / 「検索文書:」, L2 正規化済み)。
  • —faiss-cpu の IndexFlatIP (cosine 同等) で query 毎に top-16 を取得。
  • —top-16 中の gold positive (query の正解 positive 本体) を除外し、 残り 15 件を「negative 候補プール」とする。
  • —この時点では各候補が真に negative であるかは未確認 (近義 passage や同義文を含む可能性あり)。

Stage 2: cross-encoder による精選 (top-15 → top-5)

  • —cl-nagoya/ruri-v3-reranker-310m で 各 (query, candidate) の生 logit を計算する (max_length=512, FP16, sigmoid OFF / Identity activation)。
  • —positive についても同じ cross-encoder で生 logit を計算し pos_score とする。
  • —各候補に対し positive-aware filter (NV-Retriever TopK-PercPos) を適用: candidatescore < posscore × 0.95 を満たすものを PASS、そうでないものを NG。
  • —PASS 候補を score 降順にソートし、上位 5 件を negative1 〜 negative5 として確定。
  • —PASS が 5 件に満たない場合は不足分を NG 候補から score 降順でフォールバック採用する (NV-Retriever / SFR レシピ準拠の fallback 規則)。
  • —triplets の negative は同じ手順で得た PASS のうち最高スコア 1 件 (= n-tuples の negative_1 と同一エントリ)。

なお、本データセットは規模が中規模 (16,996 行) のため、Stage 1 出力の top-5 を機械的に 除外する手順 (大規模データで採用される false-positive 緩和) は実施していない。 偽 positive の混入回避は Stage 2 の positive-aware filter に完全に任せる設計。

選定統計

指標値比率
入力 (query, positive) ペア数16,996100.0%
Stage 1: ユニーク positive (FAISS index 規模)15,313-
Stage 1: gold positive が top-16 にヒット16,70298.3%
Stage 1: gold positive が rank=014,39584.7%
Stage 2: triplets が filter PASS で確定16,992100.0%
Stage 2: triplets がフォールバック (filter NG 採用)40.0%
Stage 2: n-tuples が 5 件すべて filter PASS16,94899.7%
Stage 2: n-tuples で一部フォールバック使用480.3%
累計 negative (filter PASS)84,87199.9%
累計 negative (フォールバック)1090.1%

蒸留スコア付けの方法

n-tuples / n-tuples-filtered の label カラムは、教師モデル cl-nagoya/ruri-v3-reranker-310m (Japanese ModernBERT Cross-Encoder) の 生 logit を Cross-Encoder スコアとして 各 (query, doc) ペア (positive 1 件 + negative 5 件) に付与したもの。 Stage 2 で計算した logit をそのまま label に保存している (再推論はしていない)。

スコア付けの設定

項目値
Teachercl-nagoya/ruri-v3-reranker-310m
Max length512
推論精度FP16
Batch size256
入力フォーマット単一文ペア (query, document) — Identity activation (sigmoid OFF)
出力生 logit (sigmoid 適用前)。観測 score の絶対値最大は約 12.59

KL 蒸留損失 (SparseDistillKLDivLoss / DistillKLDivLoss 等) は softmax(label / T) を target distribution として使うため、 sigmoid 後の [0, 1] 値だと softmax target が一様分布に潰れて ranking 情報が失われる。 これを防ぐため、教師の最終活性化を明示的に Identity に置き換えてスコアを出力した:

python
import torch.nn as nn
from sentence_transformers import CrossEncoder

teacher = CrossEncoder(
    "cl-nagoya/ruri-v3-reranker-310m",
    max_length=512,
    device="cuda",
    trust_remote_code=True,
)
teacher.model.eval()
teacher.model.half()                                # FP16
teacher.default_activation_function = nn.Identity()  # sigmoid を外す

scores = teacher.predict(
    [("query A", "passage A"), ("query A", "passage B")],
    batch_size=256,
    convert_to_numpy=True,
    activation_fct=nn.Identity(),  # 念のため predict() でも明示
)
# scores: array([10.32, -3.56], dtype=float32)
assert abs(scores).max() > 1.0  # |score|max > 1 で sigmoid 混入チェック

logit のスケール感:

logitsigmoid(logit)解釈
+80.9997確信度高く relevant
+50.993強く relevant
+20.881relevant 寄り
00.5不確か
-20.119irrelevant 寄り
-50.007強く irrelevant
-80.0003確信度高く irrelevant

quality_score: n-tuples-filtered の抽出指標

n-tuples-filtered は label から計算した qualityscore の valid 行のみを含む。 qualityscore は学習価値が高い行を以下の 3 軸で定義する:

  1. 1.positive が信頼できる — 教師が positive を高 logit に置く
  2. 2.偽 negative がない — max(neg) が positive を超えない
  3. 3.negative が硬い — negative の logit がそこそこ高く、margin が中程度
定義 (実装通り)
python
import numpy as np

POS_MIN        = 2.0   # σ(2.0) ≈ 0.88: 確信のある positive のみ採用
MARGIN_MIN     = 0.5   # 誤差レベルの分離は雑音と見なす
MARGIN_PENALTY = 0.1   # 易しすぎる行を弱く後回しにする係数

def quality_score_vec(labels: np.ndarray) -> np.ndarray:
    """labels: (N, 6) float32 raw logits [p, n1, n2, n3, n4, n5]. Returns (N,) float, -inf if invalid."""
    p      = labels[:, 0]
    neg    = labels[:, 1:]
    max_n  = neg.max(axis=1)
    mean_n = neg.mean(axis=1)
    margin = p - max_n

    fn_mask     = margin <= 0                                       # 偽 negative リーク
    weak_mask   = (~fn_mask) & (p < POS_MIN)                        # positive 自体が弱い
    border_mask = (~fn_mask) & (~weak_mask) & (margin < MARGIN_MIN) # 雑音疑い (境界)
    valid_mask  = (~fn_mask) & (~weak_mask) & (~border_mask)

    raw = mean_n - MARGIN_PENALTY * margin
    return np.where(valid_mask, raw, -np.inf)
各フィルタの意味
フィルタ条件意味
偽 negativemax(neg) >= positivenegative 群に実は relevant な passage が混入。KL 学習で逆効果になる。
weak positivepositive < 2.0教師が positive を「弱く relevant」としか見ていない、positive ラベル自体の信頼度が低い。
borderline0 < margin < 0.5positive と最強 negative の差が誤差程度。雑音 (近義 passage / アノテーション揺れ) の疑い。
ランキング (quality_score 本体)

mean(neg) - 0.1 × margin の 降順。本リポでは n-tuples-filtered は qualityscore の -inf 落ち (validmask 通過しない) 行のみを除外しており、 順序は元の n-tuples を保持 (top-K 抽出は行わない)。 top-K 抽出が必要な場合は下記の selecttopk を使うこと:

python
def select_top_k(ds, k: int):
    labels = np.asarray(ds["label"], dtype=np.float32)
    scores = quality_score_vec(labels)
    n_valid = int(np.isfinite(scores).sum())
    take = min(k, n_valid)
    order = np.argsort(-scores, kind="stable")[:take]
    return ds.select(order.tolist())

フィルタ統計

指標値比率
入力行数 (n-tuples)16,996100.0%
偽 negative リーク (max(neg) >= p)3472.0%
weak positive (p < 2.0)3,92723.1%
borderline (0 < margin < 0.5)2201.3%
valid (n-tuples-filtered に収録)12,50273.6%

KL 蒸留損失への組み込み (PyTorch 例)

python
import torch.nn.functional as F

def kl_distill_loss(student_logits, teacher_label, T=2.0):
    """
    student_logits: (B, 6) student の (query, doc_i) スコア (生 logit)
    teacher_label:  (B, 6) label カラムをそのまま (生 logit)
    T: distillation temperature (推奨 1.0〜4.0; 観測レンジ ±13 なので 2.0 前後)
    """
    log_p_student = F.log_softmax(student_logits / T, dim=-1)
    p_teacher     = F.softmax(teacher_label   / T, dim=-1)
    return F.kl_div(log_p_student, p_teacher, reduction="batchmean") * (T * T)

sentence-transformers の SparseDistillKLDivLoss / DistillKLDivLoss を使う場合は、 label カラムをそのまま teacher score として渡せばよい。

データソース

役割出所ライセンス
元クイズ Q&A (query / 短答)クイズワークス (https://quiz-works.com/)二次利用自由 (Free)
positive の根拠 passage 本文日本語 Wikipedia (短答から hydration 済みの状態で本リポに供給)CC BY-SA 4.0
HNM 用 dense retriever (Stage 1)cl-nagoya/ruri-v3-310mApache 2.0
蒸留教師 / Stage 2 リランカーcl-nagoya/ruri-v3-reranker-310m (Japanese ModernBERT Cross-Encoder)Apache 2.0

License

CC BY-SA 4.0

  • —元クイズ問題文 (query): クイズワークス の二次利用自由ポリシーに基づく。
  • —Passage 本文 (positive / negative_*): 日本語 Wikipedia に由来し、CC BY-SA 4.0 を継承する。再配布時は Wikipedia の表示・継承条件に従うこと。
  • —label カラム: 本リポで cl-nagoya/ruri-v3-reranker-310m を用いて算出した派生スコア。
  • —データセット全体としては、Wikipedia 由来の passage を含むため CC BY-SA 4.0 (ShareAlike) で配布する。

構築パイプライン (再現性)

pairs (16,996)  <- 上流で短答を Wikipedia passage に引き当てて hydrate 済みの (query, positive)
  ├─ unique positive 抽出 → 15,313 件
  ├─ Stage 1 (dense retriever)
  │    ├─ cl-nagoya/ruri-v3-310m で query / positive を埋め込み (L2 正規化)
  │    ├─ faiss-cpu IndexFlatIP で top-16 検索
  │    └─ gold positive を除外 → 各 query につき 15 件の negative 候補
  └─ Stage 2 (cross-encoder)
       ├─ cl-nagoya/ruri-v3-reranker-310m で (query, doc) ペアの生 logit を計算
       └─ NV-Retriever / SFR レシピで上位 5 件を確定
            ├─ triplets:          1 negative (filter PASS の最高スコア)
            ├─ n-tuples:          5 negatives + label (filter PASS 優先、不足分はフォールバック)
            └─ n-tuples-filtered: n-tuples のうち quality_score valid 行のみ

Acknowledgements

  • —元クイズデータ: クイズワークス (https://quiz-works.com/)
  • —HNM パイプライン設計: NV-Retriever (Moreira et al.) / SFR-Embedding-Mistral レシピ
  • —蒸留教師: cl-nagoya/ruri-v3-reranker-310m
  • —埋め込みモデル: cl-nagoya/ruri-v3-310m