CAFF / scripts /probe_hc3_keys.py
MrDhifallah's picture
Add files using upload-large-folder tool
da28b2a verified
Raw
History Blame Contribute Delete
5.64 kB
"""
scripts/probe_hc3_keys.py - Data-only probe (no model, no training).
Goal: determine why HC3 produces zero gradient (Full CAFF weights ==
NoHC3 weights, diff = 0.0).
The HC3 miner (caff/miners.py get_negatives_for) builds, for a
positive anchor, a key = (query_id, relation, hop) and looks for
OTHER buffered instances with the SAME key but label = 0. Those
become the negative contexts.
For HC3 to produce a useful gradient, two things must hold:
(1) There must exist (query_id, relation, hop) keys that carry
BOTH a label=1 and a label=0 instance. Otherwise the miner
returns no negatives and the loss is identically zero.
(2) Even if such keys exist, the positive and negative instances
must end up with DIFFERENT upstream context z, or the scorer
gives them identical scores and relu(s_neg - s_pos + margin)
is a constant -> zero gradient.
This probe only checks (1) here, using the dataset exactly as the
trainer builds it. It does NOT need the model. It reports how many
keys carry both labels, which directly tells us whether HC3 has any
material to learn from.
Run:
python scripts/probe_hc3_keys.py --config configs/caff_orphanet.yaml
"""
from __future__ import annotations
import argparse
import sys
from collections import defaultdict
from pathlib import Path
import yaml
ROOT = Path(__file__).parent.parent
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from caff import (
CAFFConfig,
CAFFTripleDataset,
CachedBFSExtractor,
KnowledgeGraph,
load_qa_split,
)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--config", default="configs/caff_orphanet.yaml")
ap.add_argument("--cache-dir", default="cache")
ap.add_argument("--split", default="train", choices=["train", "dev", "test"])
args = ap.parse_args()
raw = yaml.safe_load(Path(args.config).read_text(encoding="utf-8"))
cfg = CAFFConfig(**raw.get("config", {}))
kg = KnowledgeGraph.from_tsv(cfg.kg_path, min_relation_freq=cfg.min_relation_freq)
bfs = CachedBFSExtractor(kg, L=cfg.L, K_r=cfg.K_r, cache_dir=Path(args.cache_dir) / "bfs")
split_path = {"train": cfg.train_path, "dev": cfg.dev_path, "test": cfg.test_path}[args.split]
recs = load_qa_split(split_path)
ds = CAFFTripleDataset(recs, bfs, require_gold=True)
print(f"Loaded {len(ds):,} triple instances from {split_path}")
# Build the same key the HC3 miner uses: (query_id, relation, hop)
key_labels: dict[tuple, set] = defaultdict(set)
key_pos_count: dict[tuple, int] = defaultdict(int)
key_neg_count: dict[tuple, int] = defaultdict(int)
for inst in ds.instances:
key = (inst.query_id, inst.relation, inst.hop)
key_labels[key].add(inst.label)
if inst.label == 1:
key_pos_count[key] += 1
else:
key_neg_count[key] += 1
total_keys = len(key_labels)
keys_both = [k for k, labs in key_labels.items() if labs == {0, 1}]
keys_only_pos = [k for k, labs in key_labels.items() if labs == {1}]
keys_only_neg = [k for k, labs in key_labels.items() if labs == {0}]
n_pos_total = sum(key_pos_count.values())
# How many positive anchors actually have a same-key negative available?
pos_anchors_with_neg = 0
pos_anchors_total = 0
for inst in ds.instances:
if inst.label != 1:
continue
pos_anchors_total += 1
key = (inst.query_id, inst.relation, inst.hop)
if key_neg_count.get(key, 0) > 0:
pos_anchors_with_neg += 1
print()
print("=" * 70)
print("HC3 KEY ANALYSIS key = (query_id, relation, hop)")
print("=" * 70)
print(f" distinct keys : {total_keys:,}")
print(f" keys with BOTH label 0 and 1 : {len(keys_both):,}")
print(f" keys with only positives : {len(keys_only_pos):,}")
print(f" keys with only negatives : {len(keys_only_neg):,}")
print()
print(f" positive anchors total : {pos_anchors_total:,}")
print(f" positive anchors with same-key neg : {pos_anchors_with_neg:,}")
if pos_anchors_total:
pct = 100.0 * pos_anchors_with_neg / pos_anchors_total
print(f" => fraction of anchors minable : {pct:.2f}%")
print("=" * 70)
print()
if pos_anchors_with_neg == 0:
print("VERDICT (Hypothesis A confirmed):")
print(" NO positive anchor has a same-(query,relation,hop) negative.")
print(" The HC3 miner can never assemble a triplet, so the HC3 loss")
print(" is identically zero and contributes no gradient. This fully")
print(" explains why Full CAFF and NoHC3 produce identical weights.")
print()
print(" This is a DATA/DESIGN issue: the gold-labeling scheme assigns")
print(" one label per (query, relation, hop) almost everywhere, so the")
print(" 'same triple under a different context' that HC3 needs does")
print(" not exist in this KG-derived dataset.")
else:
print("VERDICT (Hypothesis A rejected):")
print(f" {pos_anchors_with_neg:,} anchors DO have same-key negatives.")
print(" HC3 has material to mine. The zero-gradient effect must then")
print(" come from identical upstream context z within a group")
print(" (Hypothesis B) - the positive and negative share the same")
print(" z_prev, so the scorer returns equal scores and relu() is")
print(" constant. That requires a model-level probe to confirm.")
print()
return 0
if __name__ == "__main__":
sys.exit(main())