add full t-SNE: 1000 golden + 200 ruler
Browse files- full_tsne.py +157 -0
full_tsne.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""把 1000 golden + 200 ruler 一起做 t-SNE 可视化。
|
| 3 |
+
|
| 4 |
+
复用 cache_emb/ 里缓存的 embedding,不重算。
|
| 5 |
+
默认 1200 个点全画出来,golden 按 GT label 着色,ruler 按 score 着色。
|
| 6 |
+
|
| 7 |
+
用法:
|
| 8 |
+
python3 full_tsne.py
|
| 9 |
+
python3 full_tsne.py --perplexity 30 --output full_tsne.png
|
| 10 |
+
python3 full_tsne.py --label-rulers boundary # 只标 boundary 附近的 ruler rank
|
| 11 |
+
python3 full_tsne.py --label-rulers all # 标全部 200 个 ruler rank(密)
|
| 12 |
+
python3 full_tsne.py --label-rulers none # 不标 rank(最干净)
|
| 13 |
+
"""
|
| 14 |
+
import argparse
|
| 15 |
+
import json
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
import pandas as pd
|
| 20 |
+
|
| 21 |
+
import matplotlib
|
| 22 |
+
matplotlib.use("Agg")
|
| 23 |
+
import matplotlib.pyplot as plt
|
| 24 |
+
from sklearn.manifold import TSNE
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
DEFAULTS = dict(
|
| 28 |
+
cache_dir = "cache_emb",
|
| 29 |
+
csv = "/mnt/bn/tns-algo-ue-my/biaowu/aipf_dm_metric/example/yss_ruler_eval/data/aipf_golden_set.csv",
|
| 30 |
+
ruler = "/mnt/bn/tns-algo-ue-my/biaowu/aipf_dm_metric/ranking_moderation/data/dm/youth_sexual_and_physical_abuse_aigt_v009/ranking_bucket/ruler_items.json",
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def load_npy_pair(cache_dir, n_csv, n_ruler, max_length=4096):
|
| 35 |
+
cd = Path(cache_dir)
|
| 36 |
+
csvs = list(cd.glob(f"csv_*_n{n_csv}_L{max_length}.npy"))
|
| 37 |
+
rulers = list(cd.glob(f"ruler_*_n{n_ruler}_L{max_length}.npy"))
|
| 38 |
+
if not csvs or not rulers:
|
| 39 |
+
raise FileNotFoundError(f"找不到缓存。期望 {cd}/csv_*_n{n_csv}_L{max_length}.npy 和 ruler_*_n{n_ruler}_L{max_length}.npy")
|
| 40 |
+
return np.load(csvs[0]), np.load(rulers[0])
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def load_ruler_meta(path):
|
| 44 |
+
with open(path) as f:
|
| 45 |
+
data = json.load(f)
|
| 46 |
+
items = data if isinstance(data, list) else (data.get("items") or data.get("ruler_items") or data.get("data") or [])
|
| 47 |
+
ranks = np.array([int(it["rank"]) for it in items])
|
| 48 |
+
scores = np.array([float(it["score"]) for it in items])
|
| 49 |
+
return ranks, scores
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def main():
|
| 53 |
+
p = argparse.ArgumentParser()
|
| 54 |
+
p.add_argument("--cache-dir", default=DEFAULTS["cache_dir"])
|
| 55 |
+
p.add_argument("--csv", default=DEFAULTS["csv"])
|
| 56 |
+
p.add_argument("--ruler", default=DEFAULTS["ruler"])
|
| 57 |
+
p.add_argument("--positive-label", default="Y")
|
| 58 |
+
p.add_argument("--boundary-rank", type=int, default=106)
|
| 59 |
+
p.add_argument("--max-length", type=int, default=4096)
|
| 60 |
+
p.add_argument("--perplexity", type=float, default=30.0)
|
| 61 |
+
p.add_argument("--output", default="full_tsne.png")
|
| 62 |
+
p.add_argument("--label-rulers", default="boundary",
|
| 63 |
+
choices=["all", "boundary", "none"],
|
| 64 |
+
help="all=全部标; boundary=只标 boundary±10 + 端点; none=不标")
|
| 65 |
+
p.add_argument("--label-fontsize", type=float, default=5)
|
| 66 |
+
return run(p.parse_args())
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def run(args):
|
| 70 |
+
print("[1] load labels and ruler meta")
|
| 71 |
+
df = pd.read_csv(args.csv, keep_default_na=False)
|
| 72 |
+
gts = df["label"].astype(str).str.upper().eq(args.positive_label.upper()).astype(int).values
|
| 73 |
+
ruler_rank, ruler_score = load_ruler_meta(args.ruler)
|
| 74 |
+
n_csv, n_ruler = len(gts), len(ruler_rank)
|
| 75 |
+
print(f" csv={n_csv}, ruler={n_ruler}")
|
| 76 |
+
|
| 77 |
+
print("[2] load cached embeddings")
|
| 78 |
+
csv_emb, ruler_emb = load_npy_pair(args.cache_dir, n_csv, n_ruler, args.max_length)
|
| 79 |
+
print(f" csv_emb={csv_emb.shape}, ruler_emb={ruler_emb.shape}")
|
| 80 |
+
|
| 81 |
+
all_emb = np.vstack([csv_emb, ruler_emb])
|
| 82 |
+
print(f"[3] t-SNE on {all_emb.shape[0]} points (perplexity={args.perplexity})")
|
| 83 |
+
tsne = TSNE(n_components=2, perplexity=args.perplexity,
|
| 84 |
+
init="pca", random_state=42, metric="cosine",
|
| 85 |
+
learning_rate="auto")
|
| 86 |
+
xy = tsne.fit_transform(all_emb)
|
| 87 |
+
csv_xy = xy[:n_csv]
|
| 88 |
+
ruler_xy = xy[n_csv:]
|
| 89 |
+
|
| 90 |
+
print(f"[4] draw to {args.output}")
|
| 91 |
+
fig, ax = plt.subplots(figsize=(16, 13), dpi=130)
|
| 92 |
+
|
| 93 |
+
# golden A(GT=0)灰色淡
|
| 94 |
+
a_mask = gts == 0
|
| 95 |
+
ax.scatter(csv_xy[a_mask, 0], csv_xy[a_mask, 1],
|
| 96 |
+
c="#bbbbbb", s=12, alpha=0.55,
|
| 97 |
+
edgecolor="none", label=f"golden A (N={a_mask.sum()})")
|
| 98 |
+
|
| 99 |
+
# golden Y(GT=1)红色
|
| 100 |
+
y_mask = gts == 1
|
| 101 |
+
ax.scatter(csv_xy[y_mask, 0], csv_xy[y_mask, 1],
|
| 102 |
+
c="#d62728", s=14, alpha=0.85,
|
| 103 |
+
edgecolor="none", label=f"golden Y (N={y_mask.sum()})")
|
| 104 |
+
|
| 105 |
+
# ruler 200 用 colormap,按 score 着色,加黑边圈以区分
|
| 106 |
+
sc = ax.scatter(ruler_xy[:, 0], ruler_xy[:, 1],
|
| 107 |
+
c=ruler_score, cmap="RdYlGn_r",
|
| 108 |
+
s=70, alpha=0.95,
|
| 109 |
+
edgecolor="black", linewidth=0.8,
|
| 110 |
+
label=f"ruler (N={n_ruler})")
|
| 111 |
+
cbar = plt.colorbar(sc, ax=ax, shrink=0.7)
|
| 112 |
+
cbar.set_label("ruler score (high = severe)")
|
| 113 |
+
|
| 114 |
+
# 标 ruler rank
|
| 115 |
+
if args.label_rulers == "all":
|
| 116 |
+
for (x, y), r in zip(ruler_xy, ruler_rank):
|
| 117 |
+
ax.annotate(str(r), (x, y), fontsize=args.label_fontsize,
|
| 118 |
+
ha="center", va="center", alpha=0.9)
|
| 119 |
+
elif args.label_rulers == "boundary":
|
| 120 |
+
# 标端点 + boundary 附近
|
| 121 |
+
targets = set([0, n_ruler - 1])
|
| 122 |
+
targets.update(range(max(0, args.boundary_rank - 10),
|
| 123 |
+
min(n_ruler, args.boundary_rank + 11)))
|
| 124 |
+
for i, ((x, y), r) in enumerate(zip(ruler_xy, ruler_rank)):
|
| 125 |
+
if int(r) in targets:
|
| 126 |
+
ax.annotate(str(r), (x, y),
|
| 127 |
+
fontsize=args.label_fontsize + 2,
|
| 128 |
+
ha="center", va="center",
|
| 129 |
+
fontweight="bold")
|
| 130 |
+
|
| 131 |
+
ax.set_title(f"t-SNE: 1000 golden + 200 ruler (Qwen3-Embedding-8B)\n"
|
| 132 |
+
f"red=GT=Y, gray=GT=A, color circles=ruler items "
|
| 133 |
+
f"(rank={'/'.join(['0', str(args.boundary_rank), str(n_ruler-1)])} highlighted)")
|
| 134 |
+
ax.set_xlabel("t-SNE 1")
|
| 135 |
+
ax.set_ylabel("t-SNE 2")
|
| 136 |
+
ax.legend(loc="best", framealpha=0.9)
|
| 137 |
+
plt.tight_layout()
|
| 138 |
+
plt.savefig(args.output, dpi=130, bbox_inches="tight")
|
| 139 |
+
print(f" saved: {args.output}")
|
| 140 |
+
|
| 141 |
+
# 顺便算一个简单的"几何分离度"
|
| 142 |
+
y_centroid = csv_xy[y_mask].mean(axis=0)
|
| 143 |
+
a_centroid = csv_xy[a_mask].mean(axis=0)
|
| 144 |
+
ruler_top = ruler_xy[ruler_rank < args.boundary_rank].mean(axis=0)
|
| 145 |
+
ruler_bot = ruler_xy[ruler_rank >= args.boundary_rank].mean(axis=0)
|
| 146 |
+
print("\n[centroids in t-SNE space]")
|
| 147 |
+
print(f" golden Y -> {y_centroid}")
|
| 148 |
+
print(f" golden A -> {a_centroid}")
|
| 149 |
+
print(f" ruler severe -> {ruler_top}")
|
| 150 |
+
print(f" ruler not -> {ruler_bot}")
|
| 151 |
+
print(f" Y-A distance : {np.linalg.norm(y_centroid - a_centroid):.2f}")
|
| 152 |
+
print(f" Y-ruler-severe : {np.linalg.norm(y_centroid - ruler_top):.2f}")
|
| 153 |
+
print(f" A-ruler-not : {np.linalg.norm(a_centroid - ruler_bot):.2f}")
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
if __name__ == "__main__":
|
| 157 |
+
main()
|