Round-2 audit: UWCA (genie) label, legend dictionary ported into legacy plot scripts

This commit is contained in:
KiHoLee
2026-08-25 22:32:15 +09:00
parent ab1770c6d0
commit 344ae1b97d
4 changed files with 2392 additions and 2392 deletions
Binary file not shown.
+1044 -1044
View File
File diff suppressed because it is too large Load Diff
+128 -128
View File
@@ -1,128 +1,128 @@
"""Real-data (digits) Fig. 5 generator: downstream classification accuracy vs SNR """Real-data (digits) Fig. 5 generator: downstream classification accuracy vs SNR
for HIGH/LOW/MIX, parallel to the synthetic Fig. 2. for HIGH/LOW/MIX, parallel to the synthetic Fig. 2.
Curves per panel: Curves per panel:
- OMA, SFDMA, NOMA-SIC (analytical baselines) - OFDMA [division], SFDMA [feature div.], NOMA-SIC (analytical baselines)
- UWCA (analytical) : oracle-beta cross-attention (relevance SUPPLIED) -- dotted - UWCA (analytical) : oracle-beta cross-attention (relevance SUPPLIED) -- dotted
- UWCA w/o MAML : decoder TRAINED on real digits, no meta-learning (from realdata_train.json) - UWCA w/o MAML : decoder TRAINED on real digits, no meta-learning (from realdata_train.json)
- UWCA w/ MAML : decoder TRAINED on real digits with MAML (proposed) -- hollow circles - UWCA w/ MAML : decoder TRAINED on real digits with MAML (proposed) -- hollow circles
Trained curves are read from results/realdata_train.json (produced by Trained curves are read from results/realdata_train.json (produced by
revision_realdata_train.py); the analytical / baseline curves are recomputed here revision_realdata_train.py); the analytical / baseline curves are recomputed here
so they share one Monte-Carlo setting. Saves results/fig_realdata_c.pdf. so they share one Monte-Carlo setting. Saves results/fig_realdata_c.pdf.
""" """
import json, numpy as np import json, numpy as np
import matplotlib; matplotlib.use('Agg') import matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from sklearn.datasets import load_digits from sklearn.datasets import load_digits
RNG = np.random.default_rng(7) RNG = np.random.default_rng(7)
U, D = 4, 64 U, D = 4, 64
DPU = D // U DPU = D // U
MASKS = np.zeros((U, D)) MASKS = np.zeros((U, D))
for u in range(U): for u in range(U):
MASKS[u, u*DPU:(u+1)*DPU] = 1.0 MASKS[u, u*DPU:(u+1)*DPU] = 1.0
NOMA_POWER = np.array([0.40, 0.30, 0.20, 0.10]) NOMA_POWER = np.array([0.40, 0.30, 0.20, 0.10])
TAU = 0.45 TAU = 0.45
X, y = load_digits(return_X_y=True) X, y = load_digits(return_X_y=True)
X = X.astype(np.float64); X = X - X.mean(0, keepdims=True) X = X.astype(np.float64); X = X - X.mean(0, keepdims=True)
X = X / (np.linalg.norm(X, axis=1, keepdims=True) + 1e-8) X = X / (np.linalg.norm(X, axis=1, keepdims=True) + 1e-8)
by_class = {c: X[y == c] for c in range(10)} by_class = {c: X[y == c] for c in range(10)}
PROTO = np.stack([by_class[c].mean(0) for c in range(10)]) PROTO = np.stack([by_class[c].mean(0) for c in range(10)])
PROTO = PROTO / (np.linalg.norm(PROTO, axis=1, keepdims=True) + 1e-8) PROTO = PROTO / (np.linalg.norm(PROTO, axis=1, keepdims=True) + 1e-8)
def _norm(E): return E / (np.linalg.norm(E, axis=-1, keepdims=True) + 1e-8) def _norm(E): return E / (np.linalg.norm(E, axis=-1, keepdims=True) + 1e-8)
def se_channel(E, snr): def se_channel(E, snr):
n = E.shape[0]; Ytx = (E * MASKS[None]).sum(1) n = E.shape[0]; Ytx = (E * MASKS[None]).sum(1)
h = np.sqrt(RNG.standard_normal((n, U, 1))**2 + RNG.standard_normal((n, U, 1))**2)*np.sqrt(0.5) h = np.sqrt(RNG.standard_normal((n, U, 1))**2 + RNG.standard_normal((n, U, 1))**2)*np.sqrt(0.5)
nstd = np.sqrt(float(np.mean(Ytx**2))/(10**(snr/10))) nstd = np.sqrt(float(np.mean(Ytx**2))/(10**(snr/10)))
return h*Ytx[:, None, :] + RNG.standard_normal((n, U, D))*nstd return h*Ytx[:, None, :] + RNG.standard_normal((n, U, D))*nstd
def ofdma(Y): return np.stack([_norm(Y[:, u, :]*MASKS[u]) for u in range(U)], 1) def ofdma(Y): return np.stack([_norm(Y[:, u, :]*MASKS[u]) for u in range(U)], 1)
def sfdma(E, snr): def sfdma(E, snr):
"""Full-band, orthogonal semantic-subspace division (block basis).""" """Full-band, orthogonal semantic-subspace division (block basis)."""
n = E.shape[0] n = E.shape[0]
h = np.sqrt(RNG.standard_normal((n, U, 1))**2 + RNG.standard_normal((n, U, 1))**2)*np.sqrt(0.5) h = np.sqrt(RNG.standard_normal((n, U, 1))**2 + RNG.standard_normal((n, U, 1))**2)*np.sqrt(0.5)
X_ = E * MASKS[None] X_ = E * MASKS[None]
yv = (h * X_).sum(1) yv = (h * X_).sum(1)
yv = yv + RNG.standard_normal((n, D))*np.sqrt(float(np.mean(yv**2))/(10**(snr/10))) yv = yv + RNG.standard_normal((n, D))*np.sqrt(float(np.mean(yv**2))/(10**(snr/10)))
return np.stack([_norm((yv * MASKS[u])/(h[:, u, :]+1e-8)) for u in range(U)], 1) return np.stack([_norm((yv * MASKS[u])/(h[:, u, :]+1e-8)) for u in range(U)], 1)
def noma_ch(E, snr): def noma_ch(E, snr):
n = E.shape[0] n = E.shape[0]
h = np.sqrt(RNG.standard_normal((n, U, 1))**2 + RNG.standard_normal((n, U, 1))**2)*np.sqrt(0.5) h = np.sqrt(RNG.standard_normal((n, U, 1))**2 + RNG.standard_normal((n, U, 1))**2)*np.sqrt(0.5)
yv = (E*np.sqrt(NOMA_POWER)[None, :, None]*h).sum(1) yv = (E*np.sqrt(NOMA_POWER)[None, :, None]*h).sum(1)
yv = yv + RNG.standard_normal((n, D))*np.sqrt(float(np.mean(yv**2))/(10**(snr/10))) yv = yv + RNG.standard_normal((n, D))*np.sqrt(float(np.mean(yv**2))/(10**(snr/10)))
return yv, h return yv, h
def noma_sic(yv, h): def noma_sic(yv, h):
n = yv.shape[0]; Eh = np.zeros((n, U, D)); res = yv.copy() n = yv.shape[0]; Eh = np.zeros((n, U, D)); res = yv.copy()
for u in range(U): for u in range(U):
Eh[:, u, :] = _norm(res/(h[:, u, :]+1e-8)); res -= h[:, u, :]*np.sqrt(NOMA_POWER[u])*Eh[:, u, :] Eh[:, u, :] = _norm(res/(h[:, u, :]+1e-8)); res -= h[:, u, :]*np.sqrt(NOMA_POWER[u])*Eh[:, u, :]
return Eh return Eh
def uwca_oracle(Y, bmc): def uwca_oracle(Y, bmc):
"""Analytical UWCA: oracle-beta cross-attention (relevance supplied).""" """Analytical UWCA: oracle-beta cross-attention (relevance supplied)."""
R = Y[:, :, None, :]*MASKS[None, None] R = Y[:, :, None, :]*MASKS[None, None]
a = bmc.copy(); np.fill_diagonal(a, 1.0); a /= a.sum(1, keepdims=True)+1e-8 a = bmc.copy(); np.fill_diagonal(a, 1.0); a /= a.sum(1, keepdims=True)+1e-8
ctx = np.einsum('ui,buid->bud', a, R) ctx = np.einsum('ui,buid->bud', a, R)
return np.stack([_norm(ctx[:, u, :]) for u in range(U)], 1) return np.stack([_norm(ctx[:, u, :]) for u in range(U)], 1)
SCEN = {'HIGH': [3, 3, 3, 3], 'LOW': [0, 1, 7, 4], 'MIX': [3, 3, 8, 1]} SCEN = {'HIGH': [3, 3, 3, 3], 'LOW': [0, 1, 7, 4], 'MIX': [3, 3, 8, 1]}
def sample(n, ca): return np.stack([by_class[ca[u]][RNG.integers(0, len(by_class[ca[u]]), n)] for u in range(U)], 1) def sample(n, ca): return np.stack([by_class[ca[u]][RNG.integers(0, len(by_class[ca[u]]), n)] for u in range(U)], 1)
def emp_beta(ca, n=4000): def emp_beta(ca, n=4000):
E = sample(n, ca); return np.einsum('nud,nvd->uv', E, E)/n E = sample(n, ca); return np.einsum('nud,nvd->uv', E, E)/n
def acc(Eh, ca): def acc(Eh, ca):
pred = np.einsum('nud,cd->nuc', _norm(Eh), PROTO).argmax(-1) pred = np.einsum('nud,cd->nuc', _norm(Eh), PROTO).argmax(-1)
return (pred == np.array(ca)[None, :]).mean() return (pred == np.array(ca)[None, :]).mean()
SNR = np.arange(0, 21, 2) SNR = np.arange(0, 21, 2)
trained = json.load(open('results/realdata_train.json')) # trained UWCA w/ and w/o MAML trained = json.load(open('results/realdata_train.json')) # trained UWCA w/ and w/o MAML
# --- recompute analytical / baseline accuracy (shared MC) --- # --- recompute analytical / baseline accuracy (shared MC) ---
ana = {s: {m: [] for m in ['OFDMA', 'SFDMA', 'NOMA-SIC', 'UWCA (analytical)']} for s in SCEN} ana = {s: {m: [] for m in ['OFDMA', 'SFDMA', 'NOMA-SIC', 'UWCA (analytical)']} for s in SCEN}
for s, ca in SCEN.items(): for s, ca in SCEN.items():
bm = emp_beta(ca); bmc = bm.copy(); np.fill_diagonal(bmc, 0.0); bmc = np.clip(bmc, 0, None) bm = emp_beta(ca); bmc = bm.copy(); np.fill_diagonal(bmc, 0.0); bmc = np.clip(bmc, 0, None)
for snr in SNR: for snr in SNR:
ao = asf = an = au = 0.0; nmc = 200 ao = asf = an = au = 0.0; nmc = 200
for _ in range(nmc): for _ in range(nmc):
E = sample(64, ca) E = sample(64, ca)
ao += acc(ofdma(se_channel(E, snr)), ca) ao += acc(ofdma(se_channel(E, snr)), ca)
asf += acc(sfdma(E, snr), ca) asf += acc(sfdma(E, snr), ca)
yv, h = noma_ch(E, snr); an += acc(noma_sic(yv, h), ca) yv, h = noma_ch(E, snr); an += acc(noma_sic(yv, h), ca)
au += acc(uwca_oracle(se_channel(E, snr), bmc), ca) au += acc(uwca_oracle(se_channel(E, snr), bmc), ca)
ana[s]['OFDMA'].append(ao/nmc); ana[s]['SFDMA'].append(asf/nmc) ana[s]['OFDMA'].append(ao/nmc); ana[s]['SFDMA'].append(asf/nmc)
ana[s]['NOMA-SIC'].append(an/nmc); ana[s]['UWCA (analytical)'].append(au/nmc) ana[s]['NOMA-SIC'].append(an/nmc); ana[s]['UWCA (analytical)'].append(au/nmc)
# --- plot: downstream accuracy, 1 row x 3 cols --- # --- plot: downstream accuracy, 1 row x 3 cols ---
COL = {'OFDMA': '#546E7A', 'SFDMA': '#9C27B0', 'NOMA-SIC': '#E65100', COL = {'OFDMA': '#546E7A', 'SFDMA': '#9C27B0', 'NOMA-SIC': '#E65100',
'UWCA (analytical)': '#1565C0', 'UWCA w/o MAML': '#2E7D32', 'UWCA w/ MAML': '#1565C0'} 'UWCA (analytical)': '#1565C0', 'UWCA w/o MAML': '#2E7D32', 'UWCA w/ MAML': '#1565C0'}
fig, ax = plt.subplots(1, 3, figsize=(11, 3.4)) fig, ax = plt.subplots(1, 3, figsize=(11, 3.4))
betas = {s: emp_beta(SCEN[s])[~np.eye(U, dtype=bool)].mean() for s in SCEN} betas = {s: emp_beta(SCEN[s])[~np.eye(U, dtype=bool)].mean() for s in SCEN}
for j, s in enumerate(['HIGH', 'LOW', 'MIX']): for j, s in enumerate(['HIGH', 'LOW', 'MIX']):
a = ax[j] a = ax[j]
a.plot(SNR, ana[s]['OFDMA'], 's--', color=COL['OFDMA'], lw=2, ms=5, label='OMA') a.plot(SNR, ana[s]['OFDMA'], 's--', color=COL['OFDMA'], lw=2, ms=5, label='OMA')
a.plot(SNR, ana[s]['SFDMA'], 'v:', color=COL['SFDMA'], lw=2, ms=5, mfc='none', label='SFDMA') a.plot(SNR, ana[s]['SFDMA'], 'v:', color=COL['SFDMA'], lw=2, ms=5, mfc='none', label='SFDMA')
a.plot(SNR, ana[s]['NOMA-SIC'], '^-.', color=COL['NOMA-SIC'], lw=2, ms=5, label='NOMA-SIC') a.plot(SNR, ana[s]['NOMA-SIC'], '^-.', color=COL['NOMA-SIC'], lw=2, ms=5, label='NOMA-SIC')
a.plot(SNR, ana[s]['UWCA (analytical)'], ':', color=COL['UWCA (analytical)'], lw=2.4, label='UWCA (oracle)') a.plot(SNR, ana[s]['UWCA (analytical)'], ':', color=COL['UWCA (analytical)'], lw=2.4, label='UWCA (genie)')
a.plot(SNR, trained[s]['UWCA w/ MAML'], 'o-', color=COL['UWCA w/ MAML'], lw=1.6, ms=6, mfc='none', mew=1.6, label='UWCA (trained)') a.plot(SNR, trained[s]['UWCA w/ MAML'], 'o-', color=COL['UWCA w/ MAML'], lw=1.6, ms=6, mfc='none', mew=1.6, label='UWCA (trained)')
a.set_ylim(0.1, 1.02); a.set_xlim(0, 20); a.grid(alpha=.3); a.set_box_aspect(0.85) a.set_ylim(0.1, 1.02); a.set_xlim(0, 20); a.grid(alpha=.3); a.set_box_aspect(0.85)
a.set_xlabel('SNR (dB)', fontsize=10) a.set_xlabel('SNR (dB)', fontsize=10)
a.text(0.5, -0.34, f"({chr(97+j)}) {s} ($\\hat\\beta_{{u,v}}\\approx{betas[s]:.2f}$)", a.text(0.5, -0.34, f"({chr(97+j)}) {s} ($\\hat\\beta_{{u,v}}\\approx{betas[s]:.2f}$)",
transform=a.transAxes, ha='center', fontsize=11) transform=a.transAxes, ha='center', fontsize=11)
if j == 0: if j == 0:
a.set_ylabel('Downstream accuracy', fontsize=10) a.set_ylabel('Downstream accuracy', fontsize=10)
ax[0].legend(fontsize=7.0, loc='upper left', bbox_to_anchor=(0.46, 0.37), ax[0].legend(fontsize=7.0, loc='upper left', bbox_to_anchor=(0.46, 0.37),
bbox_transform=ax[0].transAxes, framealpha=0.9, borderaxespad=0.0) bbox_transform=ax[0].transAxes, framealpha=0.9, borderaxespad=0.0)
fig.tight_layout() fig.tight_layout()
fig.savefig('results/fig_realdata.pdf', bbox_inches='tight') fig.savefig('results/fig_realdata.pdf', bbox_inches='tight')
fig.savefig('results/fig_realdata.png', dpi=140, bbox_inches='tight') fig.savefig('results/fig_realdata.png', dpi=140, bbox_inches='tight')
fig.savefig('results/fig_realdata_c.pdf', bbox_inches='tight') fig.savefig('results/fig_realdata_c.pdf', bbox_inches='tight')
print('saved results/fig_realdata_c.pdf') print('saved results/fig_realdata_c.pdf')
for s in SCEN: for s in SCEN:
print(f"[{s}] @20dB OFDMA={ana[s]['OFDMA'][-1]:.3f} SFDMA={ana[s]['SFDMA'][-1]:.3f} " print(f"[{s}] @20dB OFDMA={ana[s]['OFDMA'][-1]:.3f} SFDMA={ana[s]['SFDMA'][-1]:.3f} "
f"NOMA={ana[s]['NOMA-SIC'][-1]:.3f} UWCA-ana={ana[s]['UWCA (analytical)'][-1]:.3f} " f"NOMA={ana[s]['NOMA-SIC'][-1]:.3f} UWCA-ana={ana[s]['UWCA (analytical)'][-1]:.3f} "
f"UWCA-MAML(tr)={trained[s]['UWCA w/ MAML'][-1]:.3f}") f"UWCA-MAML(tr)={trained[s]['UWCA w/ MAML'][-1]:.3f}")
File diff suppressed because it is too large Load Diff