xiaohy commited on
Commit
5c47f3f
·
verified ·
1 Parent(s): 42d2d6f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +167 -86
app.py CHANGED
@@ -3,8 +3,6 @@
3
  # 1. 严格基于原版底座,UI界面和回调逻辑一字不改。
4
  # 2. 仅对所有的 fig_xxx 绘图函数进行了学术黑白化+花纹底纹改造。
5
  # 3. 修复了参数传递和括号语法问题,保证100%零报错运行。
6
- # 4. [新增] 全局设定字体为 Times New Roman + 宋体,字号统一设为五号(10.5pt)
7
- # 5. [新增] 等比例放大所有图表的 figsize,提升清晰度。
8
  # ================================================================
9
 
10
  import os
@@ -14,15 +12,39 @@ import numpy as np
14
  import matplotlib
15
  matplotlib.use('Agg')
16
  import matplotlib.pyplot as plt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  from sklearn.metrics import roc_curve, roc_auc_score
18
  import gradio as gr
19
 
20
- # 🌟 全局学术字体与字号配置 (英文 Times New Roman, 中文宋体, 五号字10.5pt)
21
- plt.rcParams['font.sans-serif'] = ['Times New Roman', 'SimSun', 'Arial']
22
- plt.rcParams['axes.unicode_minus'] = False
23
- plt.rcParams['font.size'] = 10.5
24
- plt.rcParams['hatch.linewidth'] = 1.2 # 加粗底纹线条使其更清晰
25
-
26
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
27
 
28
  # ================================================================
@@ -72,7 +94,7 @@ except FileNotFoundError:
72
  perturb_results[k]["non_member_loss_std"] = np.sqrt(0.03**2 + s**2)
73
 
74
  # ================================================================
75
- # 全局UI配置
76
  # ================================================================
77
  COLORS = {
78
  'bg': '#FFFFFF',
@@ -90,7 +112,7 @@ COLORS = {
90
  'op_colors': ['#98F5E1', '#6EE7B7', '#34D399', '#10B981', '#059669', '#047857'],
91
  }
92
 
93
- # 专门为图表新增的学术黑白配置集
94
  CHART_C = {
95
  'bg': '#FFFFFF',
96
  'panel': '#FFFFFF',
@@ -112,26 +134,54 @@ HATCH_NONMEMBER = '..'
112
  LS_LINESTYLES = ['-', '--', '-.', ':', (0, (3, 1, 1, 1))]
113
  OP_LINESTYLES = ['-', '--', '-.', ':', (0, (5, 1)), (0, (3, 1, 1, 1, 1, 1))]
114
 
115
- CHART_W = 16
 
 
 
116
 
117
  def apply_academic_style(fig, ax_or_axes):
 
 
 
 
 
 
118
  fig.patch.set_facecolor(CHART_C['bg'])
119
  axes = ax_or_axes if hasattr(ax_or_axes, '__iter__') else [ax_or_axes]
 
120
  for ax in axes:
121
  ax.set_facecolor(CHART_C['panel'])
 
122
  for spine in ax.spines.values():
123
  spine.set_color('#000000')
124
  spine.set_linewidth(1.0)
125
- ax.tick_params(colors='#000000', labelsize=10.5, width=1.0)
 
 
 
 
 
 
126
  ax.xaxis.label.set_color('#000000')
127
- ax.xaxis.label.set_fontsize(11)
128
  ax.yaxis.label.set_color('#000000')
129
- ax.yaxis.label.set_fontsize(11)
 
 
 
 
130
  ax.title.set_color('#000000')
131
- ax.title.set_fontsize(12)
132
- ax.title.set_fontweight('bold')
133
- ax.grid(True, color=CHART_C['grid'], alpha=0.8, linestyle='--', linewidth=0.5)
134
- ax.set_axisbelow(True)
 
 
 
 
 
 
 
 
135
 
136
  # ================================================================
137
  # 使用标准 Unicode ε 和 σ
@@ -191,28 +241,34 @@ for _i in range(300):
191
  acc = gu(key)/100; item[key] = bool(np.random.random()<acc)
192
  EVAL_POOL.append(item)
193
 
 
 
 
 
 
194
  # ================================================================
195
- # 图表绘制函数 (全面转换为学术黑白+底纹格式,并等比放大figsize)
196
  # ================================================================
197
  def fig_gauge(loss_val, m_mean, nm_mean, thr, m_std, nm_std):
198
- fig, ax = plt.subplots(figsize=(12, 3.5)); apply_academic_style(fig, ax)
199
  xlo = min(m_mean - 3.0 * m_std, loss_val - 0.005); xhi = max(nm_mean + 3.0 * nm_std, loss_val + 0.005)
 
200
  ax.axvspan(xlo, thr, alpha=0.3, color=CHART_C['mem'], hatch=HATCH_MEMBER, edgecolor='black')
201
  ax.axvspan(thr, xhi, alpha=0.3, color=CHART_C['nmem'], hatch=HATCH_NONMEMBER, edgecolor='black')
202
  ax.axvline(m_mean, color='black', lw=2, ls=':', zorder=2)
203
- ax.text(m_mean - 0.002, 1.02, f'Member Mean\n{m_mean:.4f}', ha='right', va='bottom', fontsize=10.5, color='black', transform=ax.get_xaxis_transform())
204
  ax.axvline(nm_mean, color='black', lw=2, ls=':', zorder=2)
205
- ax.text(nm_mean + 0.002, 1.02, f'Non-Member Mean\n{nm_mean:.4f}', ha='left', va='bottom', fontsize=10.5, color='black', transform=ax.get_xaxis_transform())
206
  ax.axvline(thr, color='black', lw=2.5, ls='--', zorder=3)
207
- ax.text(thr, 1.25, f'Threshold\n{thr:.4f}', ha='center', va='bottom', fontsize=11, fontweight='bold', color='black', transform=ax.get_xaxis_transform())
208
  ax.plot(loss_val, 0.5, marker='o', ms=16, color='black', mec='black', mew=3, zorder=5, transform=ax.get_xaxis_transform())
209
- ax.text(loss_val, 0.75, f'Current Loss\n{loss_val:.4f}', ha='center', fontsize=11, fontweight='bold', color='black', transform=ax.get_xaxis_transform())
210
- ax.text((xlo+thr)/2, 0.25, 'MEMBER', ha='center', fontsize=12, color='black', fontweight='bold', transform=ax.get_xaxis_transform(), bbox=dict(facecolor='white', alpha=0.8, edgecolor='none'))
211
- ax.text((thr+xhi)/2, 0.25, 'NON-MEMBER', ha='center', fontsize=12, color='black', fontweight='bold', transform=ax.get_xaxis_transform(), bbox=dict(facecolor='white', alpha=0.8, edgecolor='none'))
212
  ax.set_xlim(xlo, xhi); ax.set_yticks([])
213
  for s in ax.spines.values(): s.set_visible(False)
214
  ax.spines['bottom'].set_visible(True); ax.spines['bottom'].set_color('black'); ax.tick_params(colors='black', width=1)
215
- ax.set_xlabel('Loss Value', fontsize=11, color='black', fontweight='medium'); plt.tight_layout(pad=0.5)
216
  return fig
217
 
218
  def fig_auc_bar():
@@ -230,17 +286,17 @@ def fig_auc_bar():
230
  names.append(l); vals.append(perturb_results[k]['auc'])
231
  clrs.append(CHART_C['op_colors'][i]); hatches.append(HATCH_OP[i])
232
 
233
- fig, ax = plt.subplots(figsize=(16, 7.5)); apply_academic_style(fig, ax)
234
  bars = ax.bar(range(len(names)), vals, color=clrs, width=0.65, edgecolor='black', linewidth=1.5, zorder=3)
235
  for bar, h in zip(bars, hatches):
236
  if h: bar.set_hatch(h)
237
 
238
- for b,v in zip(bars, vals): ax.text(b.get_x()+b.get_width()/2, v+0.01, f'{v:.4f}', ha='center', fontsize=10.5, fontweight='semibold', color='black')
239
  ax.axhline(0.5, color='black', ls='--', lw=1.5, label='Random Guess (0.5)', zorder=2)
240
  ax.axhline(bl_auc, color='black', ls=':', lw=2, label=f'Baseline ({bl_auc:.4f})', zorder=2)
241
- ax.set_ylabel('MIA Attack AUC'); ax.set_title('Defense Effectiveness: MIA AUC Comparison', pad=20)
242
- ax.set_ylim(0.45, max(vals)+0.05); ax.set_xticks(range(len(names))); ax.set_xticklabels(names, rotation=30, ha='right', fontsize=10.5)
243
- ax.legend(facecolor='white', edgecolor='black', labelcolor='black', fontsize=10.5, loc='upper right'); plt.tight_layout()
244
  return fig
245
 
246
  def fig_radar():
@@ -249,7 +305,7 @@ def fig_radar():
249
  N = len(ms)
250
  ag = np.linspace(0, 2 * np.pi, N, endpoint=False).tolist() + [0]
251
 
252
- fig, axes = plt.subplots(1, 2, figsize=(18, 8), subplot_kw=dict(polar=True))
253
  fig.patch.set_facecolor('white')
254
 
255
  ls_cfgs = [
@@ -269,6 +325,9 @@ def fig_radar():
269
  ("OP(σ=0.03)", "perturbation_0.03")
270
  ]
271
 
 
 
 
272
  all_cfgs_for_norm = ls_cfgs + op_cfgs
273
  global_max = []
274
  for m_key in mk:
@@ -298,14 +357,14 @@ def fig_radar():
298
  ax.fill(ag, v, alpha=0.08 if ky == 'baseline' else 0.0, color='black')
299
 
300
  ax.set_xticks(ag[:-1])
301
- ax.set_xticklabels(ms, fontsize=10.5, color='black')
302
  ax.set_yticklabels([])
303
  ax.set_ylim(0, 1.05)
304
- ax.set_title(title, fontsize=12, fontweight='700', color='black', pad=18)
305
  ax.legend(
306
  loc='upper right',
307
  bbox_to_anchor=(1.35 if ax_idx == 1 else 1.30, 1.12),
308
- fontsize=10.5,
309
  framealpha=0.9,
310
  edgecolor='black'
311
  )
@@ -321,7 +380,7 @@ def fig_d3_dist_compare():
321
  ("Label Smoothing (ε=0.2)", "smooth_eps_0.2", None),
322
  ("Output Perturbation (σ=0.03)", "baseline", 0.03),
323
  ]
324
- fig, axes = plt.subplots(1, 3, figsize=(22, 6.5))
325
  apply_academic_style(fig, axes)
326
 
327
  for idx, (title, key, sigma) in enumerate(configs):
@@ -336,6 +395,7 @@ def fig_d3_dist_compare():
336
  all_v = np.concatenate([m_losses, nm_losses])
337
  bins = np.linspace(all_v.min(), all_v.max(), 35)
338
 
 
339
  ax.hist(m_losses, bins=bins, alpha=0.7, color=CHART_C['mem'], hatch=HATCH_MEMBER, label='Member', density=True, edgecolor='black', linewidth=0.8)
340
  ax.hist(nm_losses, bins=bins, alpha=0.7, color=CHART_C['nmem'], hatch=HATCH_NONMEMBER, label='Non-Member', density=True, edgecolor='black', linewidth=0.8)
341
 
@@ -344,18 +404,19 @@ def fig_d3_dist_compare():
344
  ax.axvline(m_mean, color='black', ls='--', lw=2)
345
  ax.axvline(nm_mean, color='black', ls='-', lw=2)
346
  ax.annotate(f'Gap={gap:.4f}', xy=((m_mean+nm_mean)/2, ax.get_ylim()[1]*0.85 if ax.get_ylim()[1]>0 else 5),
347
- fontsize=10.5, fontweight='bold', color='black', ha='center',
348
  bbox=dict(boxstyle='round,pad=0.4', fc='white', ec='black', alpha=1.0))
349
 
350
- ax.set_title(title, pad=15)
351
- ax.set_xlabel('Loss')
352
- if idx == 0: ax.set_ylabel('Density')
353
- ax.legend(fontsize=10.5, facecolor='white', edgecolor='black')
354
 
355
- fig.suptitle('Loss Distribution: Baseline vs LS vs OP', fontsize=14, fontweight='bold', color='black', y=1.02)
356
  plt.tight_layout(); return fig
357
 
358
  def fig_loss_dist():
 
359
  items = [
360
  (k, l, gm(k, 'auc'))
361
  for k, l in zip(LS_KEYS[1:], LS_LABELS_PLOT[1:])
@@ -367,7 +428,7 @@ def fig_loss_dist():
367
 
368
  ncols = 2
369
  nrows = int(np.ceil(n / ncols))
370
- fig, axes = plt.subplots(nrows, ncols, figsize=(14, 5.5 * nrows))
371
  axes_flat = np.array(axes).reshape(-1)
372
  apply_academic_style(fig, axes_flat)
373
 
@@ -376,13 +437,22 @@ def fig_loss_dist():
376
  nm = np.array(full_losses[k]['non_member_losses'])
377
  bins = np.linspace(min(m.min(), nm.min()), max(m.max(), nm.max()), 30)
378
 
379
- ax.hist(m, bins=bins, alpha=0.78, color=CHART_C['mem'], hatch=HATCH_MEMBER, label='Member', density=True, edgecolor='black', linewidth=0.8)
380
- ax.hist(nm, bins=bins, alpha=0.78, color=CHART_C['nmem'], hatch=HATCH_NONMEMBER, label='Non-Member', density=True, edgecolor='black', linewidth=0.8)
381
- ax.set_title(f'{l}\nAUC={a:.4f}')
382
- ax.set_xlabel('Loss')
383
- ax.set_ylabel('Density')
384
- ax.legend(fontsize=10.5, facecolor='white', edgecolor='black', labelcolor='black')
 
 
 
 
 
 
 
 
385
 
 
386
  for ax in axes_flat[n:]:
387
  ax.axis('off')
388
 
@@ -395,8 +465,9 @@ def fig_perturb_dist():
395
  ml = np.array(full_losses['baseline']['member_losses'])
396
  nl = np.array(full_losses['baseline']['non_member_losses'])
397
 
 
398
  nrows, ncols = 3, 2
399
- fig, axes = plt.subplots(nrows, ncols, figsize=(14, 16))
400
  axes_flat = axes.flatten()
401
  apply_academic_style(fig, axes_flat)
402
 
@@ -408,19 +479,27 @@ def fig_perturb_dist():
408
  v = np.concatenate([mp, np_])
409
  bins = np.linspace(v.min(), v.max(), 28)
410
 
411
- ax.hist(mp, bins=bins, alpha=0.78, color=CHART_C['mem'], hatch=HATCH_MEMBER, label='Mem+noise', density=True, edgecolor='black', linewidth=0.8)
412
- ax.hist(np_, bins=bins, alpha=0.78, color=CHART_C['nmem'], hatch=HATCH_NONMEMBER, label='Non+noise', density=True, edgecolor='black', linewidth=0.8)
 
 
 
 
 
 
 
 
413
  pa = gm(f'perturbation_{s}', 'auc')
414
- ax.set_title(f'OP(σ={s})\nAUC={pa:.4f}')
415
- ax.set_xlabel('Loss')
416
- ax.set_ylabel('Density')
417
- ax.legend(fontsize=10.5, facecolor='white', edgecolor='black', labelcolor='black')
418
 
419
  plt.tight_layout()
420
  return fig
421
 
422
  def fig_roc_curves():
423
- fig, axes = plt.subplots(1, 2, figsize=(18, 8)); apply_academic_style(fig, axes)
424
 
425
  # LS ROC
426
  ax = axes[0]
@@ -433,7 +512,7 @@ def fig_roc_curves():
433
  lw = 3.0 if k == 'baseline' else 2.0
434
  ax.plot(fpr, tpr, color='black', ls=ls_linestyle_cfgs[i], lw=lw, label=f'{l} (AUC={auc_val:.4f})')
435
  ax.plot([0,1], [0,1], '-', color='gray', lw=1.5, label='Random')
436
- ax.set_xlabel('False Positive Rate'); ax.set_ylabel('True Positive Rate'); ax.set_title('ROC Curves: Label Smoothing', pad=15); ax.legend(fontsize=10.5, facecolor='white', edgecolor='black', labelcolor='black')
437
 
438
  # OP ROC
439
  ax = axes[1]
@@ -444,11 +523,11 @@ def fig_roc_curves():
444
  rng_m = np.random.RandomState(42); rng_nm = np.random.RandomState(137); mp = ml_base + rng_m.normal(0, s, len(ml_base)); np_ = nl_base + rng_nm.normal(0, s, len(nl_base)); y_scores_p = np.concatenate([-mp, -np_]); fpr_p, tpr_p, _ = roc_curve(y_true, y_scores_p); auc_p = roc_auc_score(y_true, y_scores_p)
445
  ax.plot(fpr_p, tpr_p, color='black', ls=OP_LINESTYLES[i % len(OP_LINESTYLES)], lw=1.5, label=f'OP(σ={s}) (AUC={auc_p:.4f})')
446
  ax.plot([0,1], [0,1], '-', color='gray', lw=1.5, label='Random')
447
- ax.set_xlabel('False Positive Rate'); ax.set_ylabel('True Positive Rate'); ax.set_title('ROC Curves: Output Perturbation', pad=15); ax.legend(fontsize=10.5, facecolor='white', edgecolor='black', labelcolor='black', loc='lower right'); plt.tight_layout()
448
  return fig
449
 
450
  def fig_tpr_at_low_fpr():
451
- fig, axes = plt.subplots(1, 2, figsize=(18, 7.5)); apply_academic_style(fig, axes); labels_all, tpr5_all, tpr1_all, clrs_all, hatches_all = [], [], [], [], []
452
  ls_h_list = [HATCH_BASELINE] + HATCH_LS
453
  ls_c_list = [CHART_C['baseline']] + CHART_C['ls_colors']
454
 
@@ -463,19 +542,19 @@ def fig_tpr_at_low_fpr():
463
  bars = ax.bar(x, tpr5_all, color=clrs_all, width=0.65, edgecolor='black', linewidth=1.5, zorder=3)
464
  for bar, h in zip(bars, hatches_all):
465
  if h: bar.set_hatch(h)
466
- for b, v in zip(bars, tpr5_all): ax.text(b.get_x()+b.get_width()/2, v+0.005, f'{v:.3f}', ha='center', fontsize=10.5, fontweight='semibold', color='black')
467
- ax.set_ylabel('TPR @ 5% FPR'); ax.set_title('Attack Power at 5% FPR', pad=15); ax.set_xticks(x); ax.set_xticklabels(labels_all, rotation=35, ha='right', fontsize=10.5); ax.axhline(0.05, color='gray', ls='--', lw=2, label='Random (0.05)'); ax.legend(facecolor='white', edgecolor='black', labelcolor='black', fontsize=10.5)
468
 
469
  ax = axes[1];
470
  bars = ax.bar(x, tpr1_all, color=clrs_all, width=0.65, edgecolor='black', linewidth=1.5, zorder=3)
471
  for bar, h in zip(bars, hatches_all):
472
  if h: bar.set_hatch(h)
473
- for b, v in zip(bars, tpr1_all): ax.text(b.get_x()+b.get_width()/2, v+0.003, f'{v:.3f}', ha='center', fontsize=10.5, fontweight='semibold', color='black')
474
- ax.set_ylabel('TPR @ 1% FPR'); ax.set_title('Attack Power at 1% FPR (Strict)', pad=15); ax.set_xticks(x); ax.set_xticklabels(labels_all, rotation=35, ha='right', fontsize=10.5); ax.axhline(0.01, color='gray', ls='--', lw=2, label='Random (0.01)'); ax.legend(facecolor='white', edgecolor='black', labelcolor='black', fontsize=10.5); plt.tight_layout()
475
  return fig
476
 
477
  def fig_loss_gap_waterfall():
478
- fig, ax = plt.subplots(figsize=(16, 7.5)); apply_academic_style(fig, ax); names, gaps, clrs, hatches = [], [], [], []
479
  ls_h_list = [HATCH_BASELINE] + HATCH_LS
480
  ls_c_list = [CHART_C['baseline']] + CHART_C['ls_colors']
481
  for i, (k, l) in enumerate(zip(LS_KEYS, LS_LABELS_PLOT)):
@@ -488,8 +567,8 @@ def fig_loss_gap_waterfall():
488
  bars = ax.bar(range(len(names)), gaps, color=clrs, width=0.65, edgecolor='black', linewidth=1.5, zorder=3)
489
  for bar, h in zip(bars, hatches):
490
  if h: bar.set_hatch(h)
491
- for b, v in zip(bars, gaps): ax.text(b.get_x()+b.get_width()/2, v+0.0005, f'{v:.4f}', ha='center', fontsize=10.5, fontweight='semibold', color='black')
492
- ax.set_ylabel('Loss Gap'); ax.set_title('Member vs Non-Member Loss Gap', pad=20); ax.set_xticks(range(len(names))); ax.set_xticklabels(names, rotation=30, ha='right', fontsize=10.5); ax.annotate('Smaller gap = Better Privacy', xy=(8, gaps[0]*0.4), fontsize=10.5, color='black', fontstyle='italic', ha='center', backgroundcolor='white', bbox=dict(boxstyle='round,pad=0.4', facecolor='white', edgecolor='black', alpha=1.0)); plt.tight_layout()
493
  return fig
494
 
495
  def fig_acc_bar():
@@ -505,52 +584,52 @@ def fig_acc_bar():
505
  names.append(l); vals.append(bl_acc)
506
  clrs.append(CHART_C['op_colors'][i]); hatches.append(HATCH_OP[i])
507
 
508
- fig, ax = plt.subplots(figsize=(14, 8)); apply_academic_style(fig, ax)
509
  bars = ax.bar(range(len(names)), vals, color=clrs, width=0.65, edgecolor='black', linewidth=1.5, zorder=3)
510
  for bar, h in zip(bars, hatches):
511
  if h: bar.set_hatch(h)
512
- for b, v in zip(bars, vals): ax.text(b.get_x()+b.get_width()/2, v+1, f'{v:.1f}%', ha='center', fontsize=10.5, fontweight='bold', color='black')
513
- ax.set_ylabel('Test Accuracy (%)'); ax.set_title('Model Utility: Test Accuracy', pad=20)
514
- ax.set_ylim(0, 105); ax.set_xticks(range(len(names))); ax.set_xticklabels(names, rotation=35, ha='right', fontsize=10.5); plt.tight_layout()
515
  return fig
516
 
517
  def fig_tradeoff():
518
- fig, ax = plt.subplots(figsize=(14, 8)); apply_academic_style(fig, ax);
519
  markers_ls = ['o', 's', 'p', '*', 'h']
520
  for i, (k, l) in enumerate(zip(LS_KEYS, LS_LABELS_PLOT)):
521
  if k in mia_results and k in utility_results:
522
- ax.scatter(utility_results[k]['accuracy']*100, mia_results[k]['auc'], label=l, marker=markers_ls[i], color='white', s=280, edgecolors='black', lw=2.0, zorder=5)
523
  op_markers = ['^', 'D', 'v', 'P', 'X', '>']
524
  for i, (k, l) in enumerate(zip(OP_KEYS, OP_LABELS_PLOT)):
525
  if k in perturb_results:
526
- ax.scatter(bl_acc, perturb_results[k]['auc'], label=l, marker=op_markers[i], color='#AAAAAA', s=230, edgecolors='black', lw=1.5, zorder=6)
527
 
528
  ax.axhline(0.5, color='black', ls='--', lw=1.5, alpha=0.6, label='Random (AUC=0.5)')
529
- ax.annotate('IDEAL ZONE\nHigh Utility, Low Risk', xy=(85, 0.51), fontsize=10.5, fontweight='bold', color='black', ha='center', bbox=dict(boxstyle='round,pad=0.5', fc='white', ec='black'))
530
- ax.annotate('HIGH RISK ZONE\nLow Utility, High Risk', xy=(62, 0.61), fontsize=10.5, fontweight='bold', color='black', ha='center', bbox=dict(boxstyle='round,pad=0.5', fc='white', ec='black'))
531
- ax.set_xlabel('Model Utility (Accuracy %)'); ax.set_ylabel('Privacy Risk (MIA AUC)')
532
- ax.set_title('Privacy-Utility Trade-off Analysis', pad=20)
533
- ax.legend(fontsize=10.5, loc='lower left', ncol=2, facecolor='white', edgecolor='black', labelcolor='black'); plt.tight_layout()
534
  return fig
535
 
536
  def fig_auc_trend():
537
- fig, axes = plt.subplots(1, 2, figsize=(18, 7.5)); apply_academic_style(fig, axes); ax = axes[0]; eps_vals = [0.0, 0.02, 0.05, 0.1, 0.2]; auc_vals = [gm(k, 'auc') for k in LS_KEYS]; acc_vals = [gu(k) for k in LS_KEYS]
538
  ax2 = ax.twinx();
539
  line1 = ax.plot(eps_vals, auc_vals, marker='o', ls='-', color='black', lw=3, ms=9, label='MIA AUC (Risk)', zorder=5);
540
  line2 = ax2.plot(eps_vals, acc_vals, marker='s', ls='--', color='black', lw=3, ms=9, label='Utility % (right)', zorder=5);
541
  ax.axhline(0.5, color='gray', ls=':')
542
  ax.fill_between(eps_vals, auc_vals, 0.5, alpha=0.2, color='gray', hatch='//')
543
- ax.set_xlabel('Label Smoothing ε'); ax.set_ylabel('MIA AUC', color='black'); ax2.set_ylabel('Utility (%)', color='black'); ax.set_title('Label Smoothing Trends', pad=15); ax.tick_params(axis='y', labelcolor='black'); ax2.tick_params(axis='y', labelcolor='black'); ax2.spines['right'].set_color('black'); ax2.spines['left'].set_color('black'); lines = line1 + line2; labels = [l.get_label() for l in lines]
544
- ax.legend(lines, labels, fontsize=10.5, facecolor='white', edgecolor='black', loc='lower right')
545
 
546
  ax = axes[1]; sig_vals = OP_SIGMAS; auc_op = [gm(k, 'auc') for k in OP_KEYS];
547
  ax.plot(sig_vals, auc_op, marker='^', ls='-', color='black', lw=3, ms=9, zorder=5, label='MIA AUC');
548
  ax.axhline(bl_auc, color='black', ls='--', lw=2, label=f'Baseline ({bl_auc:.4f})');
549
  ax.axhline(0.5, color='gray', ls=':', label='Random (0.5)');
550
  ax.fill_between(sig_vals, auc_op, bl_auc, alpha=0.2, color='gray', hatch='\\\\', label='AUC Reduction')
551
- ax2r = ax.twinx(); ax2r.axhline(bl_acc, color='black', ls='-', lw=2.5); ax2r.set_ylabel(f'Utility = {bl_acc:.1f}% (unchanged)', color='black'); ax2r.set_ylim(0,100); ax2r.tick_params(axis='y', labelcolor='black'); ax2r.spines['right'].set_color('black')
552
- ax.set_xlabel('Perturbation σ'); ax.set_ylabel('MIA AUC'); ax.set_title('Output Perturbation Trends', pad=15)
553
- ax.legend(fontsize=10.5, facecolor='white', edgecolor='black', loc='lower left'); plt.tight_layout()
554
  return fig
555
 
556
  # ================================================================
@@ -749,6 +828,7 @@ footer { display: none !important; }
749
  # ================================================================
750
  # UI 布局构建 (完全不碰原版Blocks构建)
751
  # ================================================================
 
752
  with gr.Blocks(title="MIA攻防研究") as demo:
753
 
754
  gr.HTML("""<div class="title-area">
@@ -1028,4 +1108,5 @@ with gr.Blocks(title="MIA攻防研究") as demo:
1028
 
1029
  """)
1030
 
 
1031
  demo.launch(theme=gr.themes.Soft(), css=CSS)
 
3
  # 1. 严格基于原版底座,UI界面和回调逻辑一字不改。
4
  # 2. 仅对所有的 fig_xxx 绘图函数进行了学术黑白化+花纹底纹改造。
5
  # 3. 修复了参数传递和括号语法问题,保证100%零报错运行。
 
 
6
  # ================================================================
7
 
8
  import os
 
12
  import matplotlib
13
  matplotlib.use('Agg')
14
  import matplotlib.pyplot as plt
15
+
16
+ # ================================================================
17
+ # 论文图表统一格式:正文小四,图中文字按小一号处理
18
+ # 中文:宋体;英文和数字:Times New Roman
19
+ # 导出图建议使用 SVG/PDF;如需 PNG,dpi 不低于 300
20
+ # ================================================================
21
+ THESIS_DPI = 300
22
+ THESIS_FONT_CN = 'SimSun'
23
+ THESIS_FONT_EN = 'Times New Roman'
24
+ THESIS_AXIS_LABEL_SIZE = 11
25
+ THESIS_TICK_SIZE = 10
26
+ THESIS_LEGEND_SIZE = 10
27
+ THESIS_TITLE_SIZE = 12
28
+ THESIS_SUPTITLE_SIZE = 13
29
+ THESIS_ANNOT_SIZE = 10
30
+ THESIS_LINE_WIDTH = 1.4
31
+
32
+ plt.rcParams.update({
33
+ 'font.family': [THESIS_FONT_EN, THESIS_FONT_CN],
34
+ 'font.serif': [THESIS_FONT_EN, THESIS_FONT_CN],
35
+ 'font.sans-serif': [THESIS_FONT_CN, THESIS_FONT_EN],
36
+ 'mathtext.fontset': 'stix',
37
+ 'axes.unicode_minus': False,
38
+ 'figure.dpi': THESIS_DPI,
39
+ 'savefig.dpi': THESIS_DPI,
40
+ 'svg.fonttype': 'none',
41
+ 'pdf.fonttype': 42,
42
+ 'ps.fonttype': 42,
43
+ })
44
+
45
  from sklearn.metrics import roc_curve, roc_auc_score
46
  import gradio as gr
47
 
 
 
 
 
 
 
48
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
49
 
50
  # ================================================================
 
94
  perturb_results[k]["non_member_loss_std"] = np.sqrt(0.03**2 + s**2)
95
 
96
  # ================================================================
97
+ # 全局UI配置 (完全保留您的原版色彩,不影响网页和HTML元素的颜色)
98
  # ================================================================
99
  COLORS = {
100
  'bg': '#FFFFFF',
 
112
  'op_colors': ['#98F5E1', '#6EE7B7', '#34D399', '#10B981', '#059669', '#047857'],
113
  }
114
 
115
+ # 🌟 专门为图表新增的学术黑白配置集 (Hatch与线型)
116
  CHART_C = {
117
  'bg': '#FFFFFF',
118
  'panel': '#FFFFFF',
 
134
  LS_LINESTYLES = ['-', '--', '-.', ':', (0, (3, 1, 1, 1))]
135
  OP_LINESTYLES = ['-', '--', '-.', ':', (0, (5, 1)), (0, (3, 1, 1, 1, 1, 1))]
136
 
137
+ CHART_W = 15
138
+
139
+ # 让黑白底纹在论文和PDF中更清晰
140
+ plt.rcParams['hatch.linewidth'] = 1.0
141
 
142
  def apply_academic_style(fig, ax_or_axes):
143
+ """统一论文图表格式。
144
+
145
+ 导师要求:图放大一点,图中文字一般比正文小一号。
146
+ 正文为宋体小四时,图内主要文字控制在 10—11 pt 左右;
147
+ 标题略大但不过度加粗,中文使用宋体,英文和数字使用 Times New Roman。
148
+ """
149
  fig.patch.set_facecolor(CHART_C['bg'])
150
  axes = ax_or_axes if hasattr(ax_or_axes, '__iter__') else [ax_or_axes]
151
+
152
  for ax in axes:
153
  ax.set_facecolor(CHART_C['panel'])
154
+
155
  for spine in ax.spines.values():
156
  spine.set_color('#000000')
157
  spine.set_linewidth(1.0)
158
+
159
+ ax.tick_params(
160
+ colors='#000000',
161
+ labelsize=THESIS_TICK_SIZE,
162
+ width=1.0
163
+ )
164
+
165
  ax.xaxis.label.set_color('#000000')
 
166
  ax.yaxis.label.set_color('#000000')
167
+ ax.xaxis.label.set_size(THESIS_AXIS_LABEL_SIZE)
168
+ ax.yaxis.label.set_size(THESIS_AXIS_LABEL_SIZE)
169
+ ax.xaxis.label.set_fontfamily(THESIS_FONT_EN)
170
+ ax.yaxis.label.set_fontfamily(THESIS_FONT_EN)
171
+
172
  ax.title.set_color('#000000')
173
+ ax.title.set_fontsize(THESIS_TITLE_SIZE)
174
+ ax.title.set_fontweight('normal')
175
+ ax.title.set_fontfamily(THESIS_FONT_EN)
176
+
177
+ ax.grid(
178
+ True,
179
+ color=CHART_C['grid'],
180
+ alpha=0.75,
181
+ linestyle='--',
182
+ linewidth=0.5
183
+ )
184
+ ax.set_axisbelow(True)
185
 
186
  # ================================================================
187
  # 使用标准 Unicode ε 和 σ
 
241
  acc = gu(key)/100; item[key] = bool(np.random.random()<acc)
242
  EVAL_POOL.append(item)
243
 
244
+ # 可选:论文定稿时可用该函数将任意 fig 保存为 SVG/PDF 矢量图
245
+ def save_thesis_fig(fig, filename):
246
+ fig.savefig(filename, format='svg', bbox_inches='tight')
247
+ return filename
248
+
249
  # ================================================================
250
+ # 图表绘制函数 (全面转换为学术黑白+底纹格式)
251
  # ================================================================
252
  def fig_gauge(loss_val, m_mean, nm_mean, thr, m_std, nm_std):
253
+ fig, ax = plt.subplots(figsize=(11, 3.0)); apply_academic_style(fig, ax)
254
  xlo = min(m_mean - 3.0 * m_std, loss_val - 0.005); xhi = max(nm_mean + 3.0 * nm_std, loss_val + 0.005)
255
+ # 使用底纹区分判断区域
256
  ax.axvspan(xlo, thr, alpha=0.3, color=CHART_C['mem'], hatch=HATCH_MEMBER, edgecolor='black')
257
  ax.axvspan(thr, xhi, alpha=0.3, color=CHART_C['nmem'], hatch=HATCH_NONMEMBER, edgecolor='black')
258
  ax.axvline(m_mean, color='black', lw=2, ls=':', zorder=2)
259
+ ax.text(m_mean - 0.002, 1.02, f'Member Mean\n{m_mean:.4f}', ha='right', va='bottom', fontsize=THESIS_LEGEND_SIZE, color='black', transform=ax.get_xaxis_transform())
260
  ax.axvline(nm_mean, color='black', lw=2, ls=':', zorder=2)
261
+ ax.text(nm_mean + 0.002, 1.02, f'Non-Member Mean\n{nm_mean:.4f}', ha='left', va='bottom', fontsize=THESIS_LEGEND_SIZE, color='black', transform=ax.get_xaxis_transform())
262
  ax.axvline(thr, color='black', lw=2.5, ls='--', zorder=3)
263
+ ax.text(thr, 1.25, f'Threshold\n{thr:.4f}', ha='center', va='bottom', fontsize=THESIS_LEGEND_SIZE, fontweight='normal', color='black', transform=ax.get_xaxis_transform())
264
  ax.plot(loss_val, 0.5, marker='o', ms=16, color='black', mec='black', mew=3, zorder=5, transform=ax.get_xaxis_transform())
265
+ ax.text(loss_val, 0.75, f'Current Loss\n{loss_val:.4f}', ha='center', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal', color='black', transform=ax.get_xaxis_transform())
266
+ ax.text((xlo+thr)/2, 0.25, 'MEMBER', ha='center', fontsize=THESIS_AXIS_LABEL_SIZE, color='black', fontweight='normal', transform=ax.get_xaxis_transform(), bbox=dict(facecolor='white', alpha=0.8, edgecolor='none'))
267
+ ax.text((thr+xhi)/2, 0.25, 'NON-MEMBER', ha='center', fontsize=THESIS_AXIS_LABEL_SIZE, color='black', fontweight='normal', transform=ax.get_xaxis_transform(), bbox=dict(facecolor='white', alpha=0.8, edgecolor='none'))
268
  ax.set_xlim(xlo, xhi); ax.set_yticks([])
269
  for s in ax.spines.values(): s.set_visible(False)
270
  ax.spines['bottom'].set_visible(True); ax.spines['bottom'].set_color('black'); ax.tick_params(colors='black', width=1)
271
+ ax.set_xlabel('Loss Value', fontsize=THESIS_AXIS_LABEL_SIZE, color='black', fontweight='normal'); plt.tight_layout(pad=0.5)
272
  return fig
273
 
274
  def fig_auc_bar():
 
286
  names.append(l); vals.append(perturb_results[k]['auc'])
287
  clrs.append(CHART_C['op_colors'][i]); hatches.append(HATCH_OP[i])
288
 
289
+ fig, ax = plt.subplots(figsize=(15, 6.8)); apply_academic_style(fig, ax)
290
  bars = ax.bar(range(len(names)), vals, color=clrs, width=0.65, edgecolor='black', linewidth=1.5, zorder=3)
291
  for bar, h in zip(bars, hatches):
292
  if h: bar.set_hatch(h)
293
 
294
+ for b,v in zip(bars, vals): ax.text(b.get_x()+b.get_width()/2, v+0.01, f'{v:.4f}', ha='center', fontsize=THESIS_LEGEND_SIZE, fontweight='normal', color='black')
295
  ax.axhline(0.5, color='black', ls='--', lw=1.5, label='Random Guess (0.5)', zorder=2)
296
  ax.axhline(bl_auc, color='black', ls=':', lw=2, label=f'Baseline ({bl_auc:.4f})', zorder=2)
297
+ ax.set_ylabel('MIA Attack AUC', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_title('Defense Effectiveness: MIA AUC Comparison', fontsize=THESIS_SUPTITLE_SIZE, fontweight='normal', pad=20)
298
+ ax.set_ylim(0.45, max(vals)+0.05); ax.set_xticks(range(len(names))); ax.set_xticklabels(names, rotation=25, ha='right', fontsize=THESIS_AXIS_LABEL_SIZE)
299
+ ax.legend(facecolor='white', edgecolor='black', labelcolor='black', fontsize=THESIS_LEGEND_SIZE, loc='upper right'); plt.tight_layout()
300
  return fig
301
 
302
  def fig_radar():
 
305
  N = len(ms)
306
  ag = np.linspace(0, 2 * np.pi, N, endpoint=False).tolist() + [0]
307
 
308
+ fig, axes = plt.subplots(1, 2, figsize=(16, 7.6), subplot_kw=dict(polar=True))
309
  fig.patch.set_facecolor('white')
310
 
311
  ls_cfgs = [
 
325
  ("OP(σ=0.03)", "perturbation_0.03")
326
  ]
327
 
328
+ # 关键修正:两张雷达图共用同一组归一化分母。
329
+ # 原代码分别在左图和右图内部计算最大值,导致同一个 Baseline 在两张图中的形状不一致。
330
+ # 这里改为基于所有 LS 与 OP 配置计算全局最大值,使 Baseline 在左右两图中完全一致。
331
  all_cfgs_for_norm = ls_cfgs + op_cfgs
332
  global_max = []
333
  for m_key in mk:
 
357
  ax.fill(ag, v, alpha=0.08 if ky == 'baseline' else 0.0, color='black')
358
 
359
  ax.set_xticks(ag[:-1])
360
+ ax.set_xticklabels(ms, fontsize=THESIS_LEGEND_SIZE, color='black')
361
  ax.set_yticklabels([])
362
  ax.set_ylim(0, 1.05)
363
+ ax.set_title(title, fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal', color='black', pad=18)
364
  ax.legend(
365
  loc='upper right',
366
  bbox_to_anchor=(1.35 if ax_idx == 1 else 1.30, 1.12),
367
+ fontsize=THESIS_LEGEND_SIZE,
368
  framealpha=0.9,
369
  edgecolor='black'
370
  )
 
380
  ("Label Smoothing (ε=0.2)", "smooth_eps_0.2", None),
381
  ("Output Perturbation (σ=0.03)", "baseline", 0.03),
382
  ]
383
+ fig, axes = plt.subplots(1, 3, figsize=(18, 6.2))
384
  apply_academic_style(fig, axes)
385
 
386
  for idx, (title, key, sigma) in enumerate(configs):
 
395
  all_v = np.concatenate([m_losses, nm_losses])
396
  bins = np.linspace(all_v.min(), all_v.max(), 35)
397
 
398
+ # 使用高对比度的底纹
399
  ax.hist(m_losses, bins=bins, alpha=0.7, color=CHART_C['mem'], hatch=HATCH_MEMBER, label='Member', density=True, edgecolor='black', linewidth=0.8)
400
  ax.hist(nm_losses, bins=bins, alpha=0.7, color=CHART_C['nmem'], hatch=HATCH_NONMEMBER, label='Non-Member', density=True, edgecolor='black', linewidth=0.8)
401
 
 
404
  ax.axvline(m_mean, color='black', ls='--', lw=2)
405
  ax.axvline(nm_mean, color='black', ls='-', lw=2)
406
  ax.annotate(f'Gap={gap:.4f}', xy=((m_mean+nm_mean)/2, ax.get_ylim()[1]*0.85 if ax.get_ylim()[1]>0 else 5),
407
+ fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal', color='black', ha='center',
408
  bbox=dict(boxstyle='round,pad=0.4', fc='white', ec='black', alpha=1.0))
409
 
410
+ ax.set_title(title, fontsize=THESIS_TITLE_SIZE, fontweight='normal', color='black', pad=15)
411
+ ax.set_xlabel('Loss', fontsize=THESIS_AXIS_LABEL_SIZE)
412
+ if idx == 0: ax.set_ylabel('Density', fontsize=THESIS_AXIS_LABEL_SIZE)
413
+ ax.legend(fontsize=THESIS_LEGEND_SIZE, facecolor='white', edgecolor='black')
414
 
415
+ fig.suptitle('Loss Distribution: Baseline vs LS vs OP', fontsize=14, fontweight='normal', color='black', y=1.05)
416
  plt.tight_layout(); return fig
417
 
418
  def fig_loss_dist():
419
+ # 仅展示4组标签平滑模型,按“每行2张��排版,避免一行过密
420
  items = [
421
  (k, l, gm(k, 'auc'))
422
  for k, l in zip(LS_KEYS[1:], LS_LABELS_PLOT[1:])
 
428
 
429
  ncols = 2
430
  nrows = int(np.ceil(n / ncols))
431
+ fig, axes = plt.subplots(nrows, ncols, figsize=(12, 5.2 * nrows))
432
  axes_flat = np.array(axes).reshape(-1)
433
  apply_academic_style(fig, axes_flat)
434
 
 
437
  nm = np.array(full_losses[k]['non_member_losses'])
438
  bins = np.linspace(min(m.min(), nm.min()), max(m.max(), nm.max()), 30)
439
 
440
+ # Member Non-Member 使用显著不同的黑白底纹:
441
+ # Member 为斜线,Non-Member 为点状,图例与柱状图保持一致。
442
+ ax.hist(
443
+ m, bins=bins, alpha=0.78, color=CHART_C['mem'], hatch=HATCH_MEMBER,
444
+ label='Member', density=True, edgecolor='black', linewidth=0.8
445
+ )
446
+ ax.hist(
447
+ nm, bins=bins, alpha=0.78, color=CHART_C['nmem'], hatch=HATCH_NONMEMBER,
448
+ label='Non-Member', density=True, edgecolor='black', linewidth=0.8
449
+ )
450
+ ax.set_title(f'{l}\nAUC={a:.4f}', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal')
451
+ ax.set_xlabel('Loss', fontsize=THESIS_LEGEND_SIZE)
452
+ ax.set_ylabel('Density', fontsize=THESIS_LEGEND_SIZE)
453
+ ax.legend(fontsize=THESIS_LEGEND_SIZE, facecolor='white', edgecolor='black', labelcolor='black')
454
 
455
+ # 如果子图数量不足,隐藏空白坐标轴
456
  for ax in axes_flat[n:]:
457
  ax.axis('off')
458
 
 
465
  ml = np.array(full_losses['baseline']['member_losses'])
466
  nl = np.array(full_losses['baseline']['non_member_losses'])
467
 
468
+ # 6组输出扰动结果改为3行2列,保证每行两个子图,便于论文排版阅读
469
  nrows, ncols = 3, 2
470
+ fig, axes = plt.subplots(nrows, ncols, figsize=(12, 15.0))
471
  axes_flat = axes.flatten()
472
  apply_academic_style(fig, axes_flat)
473
 
 
479
  v = np.concatenate([mp, np_])
480
  bins = np.linspace(v.min(), v.max(), 28)
481
 
482
+ # 与标签平滑分布图保持一致:Member/ Mem+noise 用斜线,Non/ Non+noise 用点状
483
+ ax.hist(
484
+ mp, bins=bins, alpha=0.78, color=CHART_C['mem'], hatch=HATCH_MEMBER,
485
+ label='Mem+noise', density=True, edgecolor='black', linewidth=0.8
486
+ )
487
+ ax.hist(
488
+ np_, bins=bins, alpha=0.78, color=CHART_C['nmem'], hatch=HATCH_NONMEMBER,
489
+ label='Non+noise', density=True, edgecolor='black', linewidth=0.8
490
+ )
491
+
492
  pa = gm(f'perturbation_{s}', 'auc')
493
+ ax.set_title(f'OP(σ={s})\nAUC={pa:.4f}', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal')
494
+ ax.set_xlabel('Loss', fontsize=THESIS_LEGEND_SIZE)
495
+ ax.set_ylabel('Density', fontsize=THESIS_LEGEND_SIZE)
496
+ ax.legend(fontsize=THESIS_LEGEND_SIZE, facecolor='white', edgecolor='black', labelcolor='black')
497
 
498
  plt.tight_layout()
499
  return fig
500
 
501
  def fig_roc_curves():
502
+ fig, axes = plt.subplots(1, 2, figsize=(17, 7.4)); apply_academic_style(fig, axes)
503
 
504
  # LS ROC
505
  ax = axes[0]
 
512
  lw = 3.0 if k == 'baseline' else 2.0
513
  ax.plot(fpr, tpr, color='black', ls=ls_linestyle_cfgs[i], lw=lw, label=f'{l} (AUC={auc_val:.4f})')
514
  ax.plot([0,1], [0,1], '-', color='gray', lw=1.5, label='Random')
515
+ ax.set_xlabel('False Positive Rate', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_ylabel('True Positive Rate', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_title('ROC Curves: Label Smoothing', fontsize=THESIS_SUPTITLE_SIZE, fontweight='normal', pad=15); ax.legend(fontsize=THESIS_LEGEND_SIZE, facecolor='white', edgecolor='black', labelcolor='black')
516
 
517
  # OP ROC
518
  ax = axes[1]
 
523
  rng_m = np.random.RandomState(42); rng_nm = np.random.RandomState(137); mp = ml_base + rng_m.normal(0, s, len(ml_base)); np_ = nl_base + rng_nm.normal(0, s, len(nl_base)); y_scores_p = np.concatenate([-mp, -np_]); fpr_p, tpr_p, _ = roc_curve(y_true, y_scores_p); auc_p = roc_auc_score(y_true, y_scores_p)
524
  ax.plot(fpr_p, tpr_p, color='black', ls=OP_LINESTYLES[i % len(OP_LINESTYLES)], lw=1.5, label=f'OP(σ={s}) (AUC={auc_p:.4f})')
525
  ax.plot([0,1], [0,1], '-', color='gray', lw=1.5, label='Random')
526
+ ax.set_xlabel('False Positive Rate', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_ylabel('True Positive Rate', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_title('ROC Curves: Output Perturbation', fontsize=THESIS_SUPTITLE_SIZE, fontweight='normal', pad=15); ax.legend(fontsize=THESIS_LEGEND_SIZE, facecolor='white', edgecolor='black', labelcolor='black', loc='lower right'); plt.tight_layout()
527
  return fig
528
 
529
  def fig_tpr_at_low_fpr():
530
+ fig, axes = plt.subplots(1, 2, figsize=(17, 7.2)); apply_academic_style(fig, axes); labels_all, tpr5_all, tpr1_all, clrs_all, hatches_all = [], [], [], [], []
531
  ls_h_list = [HATCH_BASELINE] + HATCH_LS
532
  ls_c_list = [CHART_C['baseline']] + CHART_C['ls_colors']
533
 
 
542
  bars = ax.bar(x, tpr5_all, color=clrs_all, width=0.65, edgecolor='black', linewidth=1.5, zorder=3)
543
  for bar, h in zip(bars, hatches_all):
544
  if h: bar.set_hatch(h)
545
+ for b, v in zip(bars, tpr5_all): ax.text(b.get_x()+b.get_width()/2, v+0.005, f'{v:.3f}', ha='center', fontsize=THESIS_LEGEND_SIZE, fontweight='normal', color='black')
546
+ ax.set_ylabel('TPR @ 5% FPR', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_title('Attack Power at 5% FPR', fontsize=THESIS_SUPTITLE_SIZE, fontweight='normal', pad=15); ax.set_xticks(x); ax.set_xticklabels(labels_all, rotation=25, ha='right', fontsize=THESIS_AXIS_LABEL_SIZE); ax.axhline(0.05, color='gray', ls='--', lw=2, label='Random (0.05)'); ax.legend(facecolor='white', edgecolor='black', labelcolor='black', fontsize=THESIS_LEGEND_SIZE)
547
 
548
  ax = axes[1];
549
  bars = ax.bar(x, tpr1_all, color=clrs_all, width=0.65, edgecolor='black', linewidth=1.5, zorder=3)
550
  for bar, h in zip(bars, hatches_all):
551
  if h: bar.set_hatch(h)
552
+ for b, v in zip(bars, tpr1_all): ax.text(b.get_x()+b.get_width()/2, v+0.003, f'{v:.3f}', ha='center', fontsize=THESIS_LEGEND_SIZE, fontweight='normal', color='black')
553
+ ax.set_ylabel('TPR @ 1% FPR', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_title('Attack Power at 1% FPR (Strict)', fontsize=THESIS_SUPTITLE_SIZE, fontweight='normal', pad=15); ax.set_xticks(x); ax.set_xticklabels(labels_all, rotation=25, ha='right', fontsize=THESIS_AXIS_LABEL_SIZE); ax.axhline(0.01, color='gray', ls='--', lw=2, label='Random (0.01)'); ax.legend(facecolor='white', edgecolor='black', labelcolor='black', fontsize=THESIS_LEGEND_SIZE); plt.tight_layout()
554
  return fig
555
 
556
  def fig_loss_gap_waterfall():
557
+ fig, ax = plt.subplots(figsize=(15, 6.8)); apply_academic_style(fig, ax); names, gaps, clrs, hatches = [], [], [], []
558
  ls_h_list = [HATCH_BASELINE] + HATCH_LS
559
  ls_c_list = [CHART_C['baseline']] + CHART_C['ls_colors']
560
  for i, (k, l) in enumerate(zip(LS_KEYS, LS_LABELS_PLOT)):
 
567
  bars = ax.bar(range(len(names)), gaps, color=clrs, width=0.65, edgecolor='black', linewidth=1.5, zorder=3)
568
  for bar, h in zip(bars, hatches):
569
  if h: bar.set_hatch(h)
570
+ for b, v in zip(bars, gaps): ax.text(b.get_x()+b.get_width()/2, v+0.0005, f'{v:.4f}', ha='center', fontsize=THESIS_LEGEND_SIZE, fontweight='normal', color='black')
571
+ ax.set_ylabel('Loss Gap', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_title('Member vs Non-Member Loss Gap', fontsize=THESIS_SUPTITLE_SIZE, fontweight='normal', pad=20); ax.set_xticks(range(len(names))); ax.set_xticklabels(names, rotation=25, ha='right', fontsize=THESIS_AXIS_LABEL_SIZE); ax.annotate('Smaller gap = Better Privacy', xy=(8, gaps[0]*0.4), fontsize=THESIS_AXIS_LABEL_SIZE, color='black', fontstyle='italic', ha='center', backgroundcolor='white', bbox=dict(boxstyle='round,pad=0.4', facecolor='white', edgecolor='black', alpha=1.0)); plt.tight_layout()
572
  return fig
573
 
574
  def fig_acc_bar():
 
584
  names.append(l); vals.append(bl_acc)
585
  clrs.append(CHART_C['op_colors'][i]); hatches.append(HATCH_OP[i])
586
 
587
+ fig, ax = plt.subplots(figsize=(13, 7.2)); apply_academic_style(fig, ax)
588
  bars = ax.bar(range(len(names)), vals, color=clrs, width=0.65, edgecolor='black', linewidth=1.5, zorder=3)
589
  for bar, h in zip(bars, hatches):
590
  if h: bar.set_hatch(h)
591
+ for b, v in zip(bars, vals): ax.text(b.get_x()+b.get_width()/2, v+1, f'{v:.1f}%', ha='center', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal', color='black')
592
+ ax.set_ylabel('Test Accuracy (%)', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_title('Model Utility: Test Accuracy', fontsize=THESIS_SUPTITLE_SIZE, fontweight='normal', pad=20)
593
+ ax.set_ylim(0, 105); ax.set_xticks(range(len(names))); ax.set_xticklabels(names, rotation=25, ha='right', fontsize=THESIS_AXIS_LABEL_SIZE); plt.tight_layout()
594
  return fig
595
 
596
  def fig_tradeoff():
597
+ fig, ax = plt.subplots(figsize=(13, 7.2)); apply_academic_style(fig, ax);
598
  markers_ls = ['o', 's', 'p', '*', 'h']
599
  for i, (k, l) in enumerate(zip(LS_KEYS, LS_LABELS_PLOT)):
600
  if k in mia_results and k in utility_results:
601
+ ax.scatter(utility_results[k]['accuracy']*100, mia_results[k]['auc'], label=l, marker=markers_ls[i], color='white', s=250, edgecolors='black', lw=2.0, zorder=5)
602
  op_markers = ['^', 'D', 'v', 'P', 'X', '>']
603
  for i, (k, l) in enumerate(zip(OP_KEYS, OP_LABELS_PLOT)):
604
  if k in perturb_results:
605
+ ax.scatter(bl_acc, perturb_results[k]['auc'], label=l, marker=op_markers[i], color='#AAAAAA', s=200, edgecolors='black', lw=1.5, zorder=6)
606
 
607
  ax.axhline(0.5, color='black', ls='--', lw=1.5, alpha=0.6, label='Random (AUC=0.5)')
608
+ ax.annotate('IDEAL ZONE\nHigh Utility, Low Risk', xy=(85, 0.51), fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal', color='black', ha='center', bbox=dict(boxstyle='round,pad=0.5', fc='white', ec='black'))
609
+ ax.annotate('HIGH RISK ZONE\nLow Utility, High Risk', xy=(62, 0.61), fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal', color='black', ha='center', bbox=dict(boxstyle='round,pad=0.5', fc='white', ec='black'))
610
+ ax.set_xlabel('Model Utility (Accuracy %)', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_ylabel('Privacy Risk (MIA AUC)', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal')
611
+ ax.set_title('Privacy-Utility Trade-off Analysis', fontsize=THESIS_SUPTITLE_SIZE, fontweight='normal', pad=20)
612
+ ax.legend(fontsize=THESIS_AXIS_LABEL_SIZE, loc='lower left', ncol=2, facecolor='white', edgecolor='black', labelcolor='black'); plt.tight_layout()
613
  return fig
614
 
615
  def fig_auc_trend():
616
+ fig, axes = plt.subplots(1, 2, figsize=(17, 7.2)); apply_academic_style(fig, axes); ax = axes[0]; eps_vals = [0.0, 0.02, 0.05, 0.1, 0.2]; auc_vals = [gm(k, 'auc') for k in LS_KEYS]; acc_vals = [gu(k) for k in LS_KEYS]
617
  ax2 = ax.twinx();
618
  line1 = ax.plot(eps_vals, auc_vals, marker='o', ls='-', color='black', lw=3, ms=9, label='MIA AUC (Risk)', zorder=5);
619
  line2 = ax2.plot(eps_vals, acc_vals, marker='s', ls='--', color='black', lw=3, ms=9, label='Utility % (right)', zorder=5);
620
  ax.axhline(0.5, color='gray', ls=':')
621
  ax.fill_between(eps_vals, auc_vals, 0.5, alpha=0.2, color='gray', hatch='//')
622
+ ax.set_xlabel('Label Smoothing ε', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_ylabel('MIA AUC', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal', color='black'); ax2.set_ylabel('Utility (%)', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal', color='black'); ax.set_title('Label Smoothing Trends', fontsize=THESIS_SUPTITLE_SIZE, fontweight='normal', pad=15); ax.tick_params(axis='y', labelcolor='black'); ax2.tick_params(axis='y', labelcolor='black'); ax2.spines['right'].set_color('black'); ax2.spines['left'].set_color('black'); lines = line1 + line2; labels = [l.get_label() for l in lines]
623
+ ax.legend(lines, labels, fontsize=THESIS_LEGEND_SIZE, facecolor='white', edgecolor='black', loc='lower right')
624
 
625
  ax = axes[1]; sig_vals = OP_SIGMAS; auc_op = [gm(k, 'auc') for k in OP_KEYS];
626
  ax.plot(sig_vals, auc_op, marker='^', ls='-', color='black', lw=3, ms=9, zorder=5, label='MIA AUC');
627
  ax.axhline(bl_auc, color='black', ls='--', lw=2, label=f'Baseline ({bl_auc:.4f})');
628
  ax.axhline(0.5, color='gray', ls=':', label='Random (0.5)');
629
  ax.fill_between(sig_vals, auc_op, bl_auc, alpha=0.2, color='gray', hatch='\\\\', label='AUC Reduction')
630
+ ax2r = ax.twinx(); ax2r.axhline(bl_acc, color='black', ls='-', lw=2.5); ax2r.set_ylabel(f'Utility = {bl_acc:.1f}% (unchanged)', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal', color='black'); ax2r.set_ylim(0,100); ax2r.tick_params(axis='y', labelcolor='black'); ax2r.spines['right'].set_color('black')
631
+ ax.set_xlabel('Perturbation σ', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_ylabel('MIA AUC', fontsize=THESIS_AXIS_LABEL_SIZE, fontweight='normal'); ax.set_title('Output Perturbation Trends', fontsize=THESIS_SUPTITLE_SIZE, fontweight='normal', pad=15)
632
+ ax.legend(fontsize=THESIS_LEGEND_SIZE, facecolor='white', edgecolor='black', loc='lower left'); plt.tight_layout()
633
  return fig
634
 
635
  # ================================================================
 
828
  # ================================================================
829
  # UI 布局构建 (完全不碰原版Blocks构建)
830
  # ================================================================
831
+ # 移除了警告的 theme 和 css 参数,确保兼容 Gradio 6.0
832
  with gr.Blocks(title="MIA攻防研究") as demo:
833
 
834
  gr.HTML("""<div class="title-area">
 
1108
 
1109
  """)
1110
 
1111
+ # 添加了 theme 和 css 并修复了括号问题
1112
  demo.launch(theme=gr.themes.Soft(), css=CSS)