Task-level retrieval experiment (Fig 5), benchmark ban, page fit

This commit is contained in:
KiHoLee
2026-08-17 15:26:34 +09:00
parent 675dada64c
commit 2aa8ee16ea
5 changed files with 195 additions and 4 deletions
+4 -3
View File
@@ -48,9 +48,10 @@ Run the scripts from inside `code/`. All plots are rendered from
| Fig. 2 | Per-user MSE, aware vs blind floor | `revision_sims.py E1` | `floor_validation.csv` | | Fig. 2 | Per-user MSE, aware vs blind floor | `revision_sims.py E1` | `floor_validation.csv` |
| Fig. 3 | Effective sum rate at the CLIP affinity | `revision_sims.py E7a` | `rate_corrected.csv` | | Fig. 3 | Effective sum rate at the CLIP affinity | `revision_sims.py E7a` | `rate_corrected.csv` |
| Fig. 4 | Cosine recovery on real BERT+ViT pairs | `fig_real_merged.py`, then `refine_matched.py` | `bertvit_merged.csv` | | Fig. 4 | Cosine recovery on real BERT+ViT pairs | `fig_real_merged.py`, then `refine_matched.py` | `bertvit_merged.csv` |
| Fig. 5 | Receiver comparison under Rayleigh fading | `revision_sims_gpu.py E2` | `sic_comparison.csv` | | Fig. 5 | Top-1 retrieval with recovered embeddings | `retrieval_real.py` | `retrieval_real.csv` |
| Fig. 6 | Value of the measured affinity | `revision_sims.py E7a` | `beta_sweep_corrected.csv` | | Fig. 6 | Receiver comparison under Rayleigh fading | `revision_sims_gpu.py E2` | `sic_comparison.csv` |
| Fig. 7 | Multi-user scaling (joint Wiener) | `revision_sims_gpu.py E7c` | `multiuser_corrected.csv` | | Fig. 7 | Value of the measured affinity | `revision_sims.py E7a` | `beta_sweep_corrected.csv` |
| Fig. 8 | Multi-user scaling (joint Wiener) | `revision_sims_gpu.py E7c` | `multiuser_corrected.csv` |
Quantities quoted in the text but not plotted come from the same Quantities quoted in the text but not plotted come from the same
drivers: `revision_sims.py E0` writes `theorem_check.csv` (Theorem 1 drivers: `revision_sims.py E0` writes `theorem_check.csv` (Theorem 1
+30 -1
View File
@@ -179,11 +179,40 @@ def fig_multiuser():
save(fig, "fig_multiuser_corrected") save(fig, "fig_multiuser_corrected")
# ------------------------------------------------ fig_retrieval
def fig_retrieval():
rows = rows_of("retrieval_real")
snr = col(rows, "snr_db")
fig, ax = plt.subplots()
ax.plot(snr, col(rows, "edma"), "o-", color="C3", label=LBL["edma"])
ax.plot(snr, col(rows, "hybrid"), "^-", color="C2",
label=LBL["hybrid"])
ax.plot(snr, col(rows, "todma"), "d-.", color="C4",
label=LBL["todma"])
ax.plot(snr, col(rows, "oma"), "v:", color="C1", label=LBL["oma"])
ax.plot(snr, col(rows, "genie"), "-", color="gray", lw=1.0,
label=LBL["genie"])
ax.axhline(1.0 / 16, color="gray", ls=":", lw=0.8)
ax.annotate("chance $1/16$", xy=(10.5, 1.0 / 16 + 0.015), fontsize=7,
color="gray")
ax.set_xlabel("SNR $\\rho$ [dB]")
ax.set_ylabel("Top-1 retrieval accuracy")
ax.set_xlim(snr[0], snr[-1]); ax.set_ylim(0, 1.42)
ax.set_yticks([0.0, 0.2, 0.4, 0.6, 0.8, 1.0])
hs, ls = ax.get_legend_handles_labels()
order = [1, 4, 0, 2, 3] # long labels share column one
ax.legend([hs[i] for i in order], [ls[i] for i in order],
loc="upper center", ncol=2, columnspacing=0.7,
handlelength=1.3, handletextpad=0.5)
save(fig, "fig_retrieval")
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys
todo = set(sys.argv[1:]) todo = set(sys.argv[1:])
ALL = {"floor": fig_floor, "rate": fig_rate, "beta": fig_beta_sweep, ALL = {"floor": fig_floor, "rate": fig_rate, "beta": fig_beta_sweep,
"sic": fig_sic, "multi": fig_multiuser} "sic": fig_sic, "multi": fig_multiuser,
"retr": fig_retrieval}
for name, fn in ALL.items(): for name, fn in ALL.items():
if not todo or name in todo: if not todo or name in todo:
fn() fn()
+147
View File
@@ -0,0 +1,147 @@
"""
Task-level validation: nearest-neighbor retrieval with recovered
embeddings on the real BERT/ViT pairs.
===================================================================
For every recovered embedding, rank the 16 clean database embeddings
of the SAME modality by absolute cosine and score top-1 retrieval of
the transmitted item (chance level 1/16). This measures whether the
recovery preserves semantic identity, the operational question behind
the cosine metric of the manuscript.
Schemes: EDMA (aware Wiener), hybrid (EDMA + stored refinement gate
from data/refine_gates.npz), OMA (equivalent-bandwidth model),
genie-aided SIC bound, ToDMA-adapted (first 40 fading draws).
Same channel, energy, and seed conventions as fig_real_merged.py.
Writes data/retrieval_real.csv. Run under WSL (torch, CUDA).
"""
from __future__ import annotations
import csv
import math
import time
import numpy as np
import torch
from fig_real_merged import (load_pairs, haar_t, aware_batch, todma_prepare,
omp_code, todma_run, SNRS, NFADE, NFADE_TOD,
D, DATA, DEV, SEED)
torch.manual_seed(SEED)
rng = np.random.default_rng(SEED)
def refine_apply_single(P1, z):
"""z: (b, D) real torch tensor; P1: (D, D) gate."""
return D * torch.softmax((z @ P1.T) / math.sqrt(D), dim=1) * z
def top1(rec, db, idx):
"""rec: (b, D) cfloat; db: (n, D) float; returns (b,) 0/1 hits."""
sims = (rec @ db.T.to(rec.dtype).conj()).abs() # (b, n)
sims = sims / (rec.norm(dim=1, keepdim=True)
* db.norm(dim=1).unsqueeze(0))
return (sims.argmax(dim=1) == idx).float().cpu().numpy()
def main():
A, B, betas = load_pairs()
npairs = len(A)
gates = np.load(DATA / "refine_gates.npz")
P1 = torch.tensor(gates["P1"], dtype=torch.float32, device=DEV)
tod = todma_prepare()
codes = [(omp_code(tod[0], A[i], tod[3]),
omp_code(tod[0], B[i], tod[3])) for i in range(npairs)]
At = torch.tensor(A, dtype=torch.float32, device=DEV)
Bt = torch.tensor(B, dtype=torch.float32, device=DEV)
gen = torch.Generator(device=DEV).manual_seed(SEED)
nb = len(SNRS)
sigs = torch.tensor(10 ** (-SNRS / 20.0), dtype=torch.float32,
device=DEV)
keys = ("edma", "hybrid", "oma", "genie", "todma")
acc = {k: np.zeros(nb) for k in keys}
cnt = {k: np.zeros(nb) for k in keys}
t0 = time.time()
for i in range(npairs):
bi = float(betas[i])
e1, e2 = At[i], Bt[i]
c1c, c2c = codes[i]
for f in range(NFADE):
M = haar_t(2, gen)
M1, M2 = M[0], M[1]
Q = M1.T @ M2
h = (torch.randn(2, generator=gen, device=DEV)
+ 1j * torch.randn(2, generator=gen, device=DEV)) \
/ math.sqrt(2)
n = (torch.randn(D, generator=gen, device=DEV)
+ 1j * torch.randn(D, generator=gen, device=DEV)) \
/ math.sqrt(2)
n2 = (torch.randn(D, generator=gen, device=DEV)
+ 1j * torch.randn(D, generator=gen, device=DEV)) \
/ math.sqrt(2)
r0 = h[0] * (M1 @ e1).to(torch.cfloat) \
+ h[1] * (M2 @ e2).to(torch.cfloat)
r = r0.unsqueeze(0) + sigs.view(-1, 1) * n.unsqueeze(0)
t1 = (M1.T.to(torch.cfloat) @ r.unsqueeze(-1)).squeeze(-1) / h[0]
t2 = (M2.T.to(torch.cfloat) @ r.unsqueeze(-1)).squeeze(-1) / h[1]
c1 = (h[1] / h[0]).item()
c2 = (h[0] / h[1]).item()
v1 = sigs**2 / h[0].abs()**2
v2 = sigs**2 / h[1].abs()**2
g1 = aware_batch(t1, Q, bi, c1, v1)
g2 = aware_batch(t2, Q.T, bi, c2, v2)
acc["edma"] += 0.5 * (top1(g1, At, i) + top1(g2, Bt, i))
hy1 = refine_apply_single(P1, g1.real.float()).to(torch.cfloat)
hy2 = refine_apply_single(P1, g2.real.float()).to(torch.cfloat)
acc["hybrid"] += 0.5 * (top1(hy1, At, i) + top1(hy2, Bt, i))
o1 = e1.to(torch.cfloat).unsqueeze(0) \
+ math.sqrt(2) * sigs.view(-1, 1) * n.unsqueeze(0) / h[0]
o2 = e2.to(torch.cfloat).unsqueeze(0) \
+ math.sqrt(2) * sigs.view(-1, 1) * n2.unsqueeze(0) / h[1]
acc["oma"] += 0.5 * (top1(o1, At, i) + top1(o2, Bt, i))
ge1 = (M1.T.to(torch.cfloat)
@ (r - h[1] * (M2 @ e2).to(torch.cfloat)).unsqueeze(-1)
).squeeze(-1) / h[0]
ge2 = (M2.T.to(torch.cfloat)
@ (r - h[0] * (M1 @ e1).to(torch.cfloat)).unsqueeze(-1)
).squeeze(-1) / h[1]
acc["genie"] += 0.5 * (top1(ge1, At, i) + top1(ge2, Bt, i))
for kk in ("edma", "hybrid", "oma", "genie"):
cnt[kk] += 1
if f < NFADE_TOD:
hnp = (complex(h[0].item()), complex(h[1].item()))
nslots = [(rng.standard_normal(tod[4])
+ 1j * rng.standard_normal(tod[4]))
/ math.sqrt(2) for _ in range(tod[3])]
for k, s in enumerate(SNRS):
sig = 10 ** (-s / 20.0)
recs = todma_run(tod, (c1c, c2c), hnp, sig, nslots)
hit = 0.0
for j, (rec, db, ii) in enumerate(
((recs[0], A, i), (recs[1], B, i))):
if rec is None:
continue # failed detection: no hit
sims = np.abs(db @ rec) / (
np.linalg.norm(db, axis=1)
* np.linalg.norm(rec))
hit += 0.5 * float(int(np.argmax(sims)) == ii)
acc["todma"][k] += hit
cnt["todma"][k] += 1
print(f" pair {i+1}/{npairs} done ({time.time()-t0:.0f}s)",
flush=True)
for k in keys:
acc[k] /= np.maximum(cnt[k], 1)
with open(DATA / "retrieval_real.csv", "w", newline="") as f:
w = csv.writer(f)
w.writerow(["snr_db"] + list(keys))
for k, s in enumerate(SNRS):
w.writerow([s] + [acc[key][k] for key in keys])
print(f"[OK] wrote {DATA/'retrieval_real.csv'}")
for k, s in enumerate(SNRS):
print(f" {s:4.1f} dB EDMA {acc['edma'][k]:.3f} "
f"hybrid {acc['hybrid'][k]:.3f} ToDMA {acc['todma'][k]:.3f} "
f" OMA {acc['oma'][k]:.3f} genie {acc['genie'][k]:.3f}")
if __name__ == "__main__":
main()
+14
View File
@@ -0,0 +1,14 @@
snr_db,edma,hybrid,oma,genie,todma
0.0,0.1915625,0.2253125,0.1278125,0.190625,0.028125
2.5,0.278125,0.325,0.1884375,0.2840625,0.03671875
5.0,0.4065625,0.45375,0.2784375,0.4103125,0.05390625
7.5,0.5365625,0.5765625,0.3965625,0.54,0.0765625
10.0,0.6625,0.6940625,0.52375,0.6675,0.1390625
12.5,0.7646875,0.781875,0.6515625,0.76375,0.23359375
15.0,0.8296875,0.84875,0.7515625,0.833125,0.371875
17.5,0.8828125,0.8934375,0.828125,0.89,0.515625
20.0,0.9178125,0.926875,0.881875,0.925625,0.65859375
22.5,0.94625,0.9503125,0.9175,0.9565625,0.7703125
25.0,0.96125,0.9625,0.94625,0.9734375,0.84609375
27.5,0.970625,0.971875,0.9690625,0.983125,0.8921875
30.0,0.9796875,0.98,0.9809375,0.99125,0.91640625
1 snr_db edma hybrid oma genie todma
2 0.0 0.1915625 0.2253125 0.1278125 0.190625 0.028125
3 2.5 0.278125 0.325 0.1884375 0.2840625 0.03671875
4 5.0 0.4065625 0.45375 0.2784375 0.4103125 0.05390625
5 7.5 0.5365625 0.5765625 0.3965625 0.54 0.0765625
6 10.0 0.6625 0.6940625 0.52375 0.6675 0.1390625
7 12.5 0.7646875 0.781875 0.6515625 0.76375 0.23359375
8 15.0 0.8296875 0.84875 0.7515625 0.833125 0.371875
9 17.5 0.8828125 0.8934375 0.828125 0.89 0.515625
10 20.0 0.9178125 0.926875 0.881875 0.925625 0.65859375
11 22.5 0.94625 0.9503125 0.9175 0.9565625 0.7703125
12 25.0 0.96125 0.9625 0.94625 0.9734375 0.84609375
13 27.5 0.970625 0.971875 0.9690625 0.983125 0.8921875
14 30.0 0.9796875 0.98 0.9809375 0.99125 0.91640625
Binary file not shown.