Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,12 +9,10 @@ import gradio as gr
|
|
| 9 |
|
| 10 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 11 |
|
| 12 |
-
|
| 13 |
def load_json(path):
|
| 14 |
with open(os.path.join(BASE_DIR, path), 'r', encoding='utf-8') as f:
|
| 15 |
return json.load(f)
|
| 16 |
|
| 17 |
-
|
| 18 |
def clean_text(text):
|
| 19 |
if not isinstance(text, str):
|
| 20 |
return str(text)
|
|
@@ -23,7 +21,6 @@ def clean_text(text):
|
|
| 23 |
text = re.sub(r'[\u200b-\u200f\u2028-\u202f\u2060-\u206f\ufeff]', '', text)
|
| 24 |
return text.strip()
|
| 25 |
|
| 26 |
-
|
| 27 |
member_data = load_json("data/member.json")
|
| 28 |
non_member_data = load_json("data/non_member.json")
|
| 29 |
mia_results = load_json("results/mia_results.json")
|
|
@@ -90,7 +87,6 @@ for _i in range(300):
|
|
| 90 |
EVAL_POOL.append({'question':_q,'answer':_ans,'type_cn':TYPE_CN[_t],
|
| 91 |
'baseline':bool(np.random.random()<bl_acc/100),'smooth_0.02':bool(np.random.random()<s002_acc/100),'smooth_0.2':bool(np.random.random()<s02_acc/100)})
|
| 92 |
|
| 93 |
-
|
| 94 |
# ══════════════ 图表 ══════════════
|
| 95 |
|
| 96 |
def fig_gauge(loss_val, m_mean, nm_mean, thr, m_std, nm_std):
|
|
@@ -112,7 +108,6 @@ def fig_gauge(loss_val, m_mean, nm_mean, thr, m_std, nm_std):
|
|
| 112 |
for s in ['top','right','left']: ax.spines[s].set_visible(False)
|
| 113 |
ax.set_xlabel('Loss Value', fontsize=9); plt.tight_layout(); return fig
|
| 114 |
|
| 115 |
-
|
| 116 |
def fig_loss_dist():
|
| 117 |
items = [(k,l,mia_results.get(k,{}).get('auc',0)) for k,l in [('baseline','Baseline'),('smooth_0.02',u'LS(\u03b5=0.02)'),('smooth_0.2',u'LS(\u03b5=0.2)')] if k in full_results]
|
| 118 |
n = len(items)
|
|
@@ -129,7 +124,6 @@ def fig_loss_dist():
|
|
| 129 |
ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
|
| 130 |
plt.tight_layout(); return fig
|
| 131 |
|
| 132 |
-
|
| 133 |
def fig_perturb_dist():
|
| 134 |
base=full_results.get('baseline',{})
|
| 135 |
if not base: return plt.figure()
|
|
@@ -148,7 +142,6 @@ def fig_perturb_dist():
|
|
| 148 |
ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
|
| 149 |
plt.tight_layout(); return fig
|
| 150 |
|
| 151 |
-
|
| 152 |
def fig_auc_bar():
|
| 153 |
data=[]
|
| 154 |
for k,n,c in [('baseline','Baseline','#64748b'),(u'smooth_0.02',u'LS(\u03b5=0.02)','#3b82f6'),('smooth_0.2',u'LS(\u03b5=0.2)','#1d4ed8')]:
|
|
@@ -163,7 +156,6 @@ def fig_auc_bar():
|
|
| 163 |
ax.legend(fontsize=9); ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
|
| 164 |
ax.grid(axis='y',alpha=.15); plt.xticks(fontsize=10); plt.tight_layout(); return fig
|
| 165 |
|
| 166 |
-
|
| 167 |
def fig_acc_bar():
|
| 168 |
data=[]
|
| 169 |
for k,n,c in [('baseline','Baseline','#64748b'),('smooth_0.02',u'LS(\u03b5=0.02)','#3b82f6'),('smooth_0.2',u'LS(\u03b5=0.2)','#1d4ed8')]:
|
|
@@ -178,7 +170,6 @@ def fig_acc_bar():
|
|
| 178 |
ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
|
| 179 |
ax.grid(axis='y',alpha=.15); plt.xticks(fontsize=10); plt.tight_layout(); return fig
|
| 180 |
|
| 181 |
-
|
| 182 |
def fig_tradeoff():
|
| 183 |
fig,ax=plt.subplots(figsize=(9,6.5)); pts=[]
|
| 184 |
for k,n,mk,c in [('baseline','Baseline','o','#64748b'),('smooth_0.02',u'LS(\u03b5=0.02)','s','#3b82f6'),('smooth_0.2',u'LS(\u03b5=0.2)','s','#1d4ed8')]:
|
|
@@ -195,20 +186,18 @@ def fig_tradeoff():
|
|
| 195 |
ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
|
| 196 |
plt.tight_layout(); return fig
|
| 197 |
|
| 198 |
-
|
| 199 |
# ══════════════ 回调 ══════════════
|
| 200 |
|
| 201 |
def cb_sample(src):
|
| 202 |
pool=member_data if src=="成员数据(训练集)" else non_member_data
|
| 203 |
s=pool[np.random.randint(len(pool))]; m=s['metadata']
|
| 204 |
tm={'calculation':'基础计算','word_problem':'应用题','concept':'概念问答','error_correction':'错题订正'}
|
| 205 |
-
md=("| 字段 | 值 |\n|---|---|\n| 姓名 | "+clean_text(str(m.get('name','')))+
|
| 206 |
-
" |\n| 学号 | "+clean_text(str(m.get('student_id','')))+
|
| 207 |
-
" |\n| 班级 | "+clean_text(str(m.get('class','')))+
|
| 208 |
-
" |\n| 成绩 | "+clean_text(str(m.get('score','')))+" 分 |\n| 类型 | "+tm.get(s.get('task_type',''),'')+" |\n")
|
| 209 |
return md, clean_text(s.get('question','')), clean_text(s.get('answer',''))
|
| 210 |
|
| 211 |
-
|
| 212 |
ATK_MAP = {
|
| 213 |
u"基线模型 (Baseline)":"baseline",
|
| 214 |
u"标签平滑 (\u03b5=0.02)":"smooth_0.02",
|
|
@@ -218,7 +207,6 @@ ATK_MAP = {
|
|
| 218 |
u"输出扰动 (\u03c3=0.02)":"perturbation_0.02",
|
| 219 |
}
|
| 220 |
|
| 221 |
-
|
| 222 |
def cb_attack(idx, src, target):
|
| 223 |
is_mem = src=="成员数据(训练集)"
|
| 224 |
pool = member_data if is_mem else non_member_data
|
|
@@ -243,42 +231,45 @@ def cb_attack(idx, src, target):
|
|
| 243 |
pl,pc=("训练成员","🔴") if pred else ("非训练成员","🟢")
|
| 244 |
al,ac=("训练成员","🔴") if is_mem else ("非训练成员","🟢")
|
| 245 |
if correct and pred and is_mem:
|
| 246 |
-
v="⚠️ **攻击成功:隐私泄露**\n\n模型对该
|
| 247 |
elif correct:
|
| 248 |
-
v="✅ **判定正确**\n\n攻击者的判定与真实身份一致。"
|
| 249 |
else:
|
| 250 |
-
v="🛡️ **防御成功**\n\n攻击者的判定错误,防御起到了保护作用。"
|
| 251 |
-
res=(v+"\n\n**攻击目标**: "+lbl+" | **AUC**: "+f"{auc_v:.4f}"+"\n\n"
|
| 252 |
"| | 攻击者判定 | 真实身份 |\n|---|---|---|\n"
|
| 253 |
"| 身份 | "+pc+" "+pl+" | "+ac+" "+al+" |\n"
|
| 254 |
"| Loss | "+f"{loss:.4f}"+" | 阈值: "+f"{thr:.4f}"+" |\n")
|
| 255 |
-
qtxt="**样本 #"+str(idx)+"**\n\n"+clean_text(sample.get('question',''))[:500]
|
| 256 |
return qtxt, gauge, res
|
| 257 |
|
| 258 |
-
|
| 259 |
EVAL_ACC={u"基线模型":bl_acc,u"标签平滑 (\u03b5=0.02)":s002_acc,u"标签平滑 (\u03b5=0.2)":s02_acc,
|
| 260 |
u"输出扰动 (\u03c3=0.01)":bl_acc,u"输出扰动 (\u03c3=0.015)":bl_acc,u"输出扰动 (\u03c3=0.02)":bl_acc}
|
| 261 |
EVAL_KEY={u"基线模型":"baseline",u"标签平滑 (\u03b5=0.02)":"smooth_0.02",u"标签平滑 (\u03b5=0.2)":"smooth_0.2",
|
| 262 |
u"输出扰动 (\u03c3=0.01)":"baseline",u"输出扰动 (\u03c3=0.015)":"baseline",u"输出扰动 (\u03c3=0.02)":"baseline"}
|
| 263 |
|
| 264 |
-
|
| 265 |
def cb_eval(model):
|
| 266 |
k=EVAL_KEY.get(model,"baseline"); acc=EVAL_ACC.get(model,bl_acc)
|
| 267 |
q=EVAL_POOL[np.random.randint(len(EVAL_POOL))]; ok=q.get(k,q.get('baseline',False))
|
| 268 |
ic="✅ 正确" if ok else "❌ 错误"
|
| 269 |
-
note="\n\n> 输出扰动不改变模型参数,准确率与基线一致。" if u"\u03c3" in model else ""
|
| 270 |
-
return ("**"+model+"
|
| 271 |
"| 项目 | 内容 |\n|---|---|\n"
|
| 272 |
"| 类型 | "+q['type_cn']+" |\n| 题目 | "+q['question']+" |\n"
|
| 273 |
"| 正确答案 | "+q['answer']+" |\n| 判定 | "+ic+" |"+note)
|
| 274 |
|
| 275 |
|
| 276 |
-
# ══════════════
|
| 277 |
|
| 278 |
CSS = """
|
| 279 |
-
/* 1.
|
| 280 |
body {
|
| 281 |
-
background-color: #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important;
|
| 283 |
}
|
| 284 |
.gradio-container {
|
|
@@ -286,14 +277,14 @@ body {
|
|
| 286 |
margin: 40px auto !important;
|
| 287 |
}
|
| 288 |
|
| 289 |
-
/* 2.
|
| 290 |
.title-area {
|
| 291 |
background: #ffffff;
|
| 292 |
-
padding:
|
| 293 |
border-radius: 12px;
|
| 294 |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
| 295 |
-
margin-bottom:
|
| 296 |
-
border-left: 6px solid #2563eb;
|
| 297 |
text-align: left;
|
| 298 |
}
|
| 299 |
.title-area h1 {
|
|
@@ -310,29 +301,28 @@ body {
|
|
| 310 |
font-weight: 500;
|
| 311 |
}
|
| 312 |
|
| 313 |
-
/* 3.
|
| 314 |
.tabitem {
|
| 315 |
-
background:
|
| 316 |
border-radius: 0 0 12px 12px !important;
|
| 317 |
border: 1px solid #e2e8f0 !important;
|
| 318 |
border-top: none !important;
|
| 319 |
-
box-shadow: 0 4px
|
| 320 |
padding: 32px 40px !important;
|
| 321 |
|
| 322 |
-
/* 就是这两行代码,让你的每一页大小绝对一致 */
|
| 323 |
height: 760px !important;
|
| 324 |
max-height: 760px !important;
|
| 325 |
overflow-y: auto !important;
|
| 326 |
overflow-x: hidden !important;
|
| 327 |
}
|
| 328 |
|
| 329 |
-
/*
|
| 330 |
.tabitem::-webkit-scrollbar { width: 6px; }
|
| 331 |
.tabitem::-webkit-scrollbar-track { background: transparent; }
|
| 332 |
.tabitem::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
|
| 333 |
.tabitem::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
| 334 |
|
| 335 |
-
/* 4. Tab 导航栏
|
| 336 |
.tab-nav {
|
| 337 |
border-bottom: none !important;
|
| 338 |
gap: 4px !important;
|
|
@@ -343,7 +333,7 @@ body {
|
|
| 343 |
padding: 12px 24px !important;
|
| 344 |
font-weight: 600 !important;
|
| 345 |
color: #64748b !important;
|
| 346 |
-
background: #
|
| 347 |
border: 1px solid #e2e8f0 !important;
|
| 348 |
border-bottom: none !important;
|
| 349 |
border-radius: 10px 10px 0 0 !important;
|
|
@@ -361,7 +351,7 @@ body {
|
|
| 361 |
box-shadow: 0 -4px 6px -2px rgba(0,0,0,0.02) !important;
|
| 362 |
}
|
| 363 |
|
| 364 |
-
/* 5. 内部排版
|
| 365 |
.prose h2 {
|
| 366 |
font-size: 1.3rem !important;
|
| 367 |
color: #0f172a !important;
|
|
@@ -404,7 +394,7 @@ body {
|
|
| 404 |
.prose tr:last-child td { border-bottom: none !important; }
|
| 405 |
.prose tr:hover td { background: #f0f9ff !important; }
|
| 406 |
|
| 407 |
-
/* 7. 纯色
|
| 408 |
button.primary {
|
| 409 |
background: #2563eb !important;
|
| 410 |
color: white !important;
|
|
@@ -421,7 +411,7 @@ button.primary:hover {
|
|
| 421 |
box-shadow: 0 6px 10px -1px rgba(37, 99, 235, 0.3) !important;
|
| 422 |
}
|
| 423 |
|
| 424 |
-
/* 8. 提示
|
| 425 |
.prose blockquote {
|
| 426 |
border-left: 4px solid #3b82f6 !important;
|
| 427 |
background: #eff6ff !important;
|
|
@@ -432,165 +422,193 @@ button.primary:hover {
|
|
| 432 |
margin: 1.5em 0 !important;
|
| 433 |
}
|
| 434 |
|
| 435 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
footer { display: none !important; }
|
| 437 |
"""
|
| 438 |
|
| 439 |
with gr.Blocks(title="MIA攻防研究", theme=gr.themes.Base(), css=CSS) as demo:
|
| 440 |
|
| 441 |
gr.HTML("""<div class="title-area">
|
| 442 |
-
<h1>教育大模型中的成员推理攻击及其防御研究</h1>
|
| 443 |
-
<p>Membership Inference Attack & Defense on Educational LLM</p>
|
| 444 |
</div>""")
|
| 445 |
|
| 446 |
-
# ═══════ Tab 1 ═══════
|
| 447 |
-
with gr.Tab("实验总览"):
|
| 448 |
-
gr.Markdown(
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
with gr.Row(equal_height=True):
|
| 488 |
with gr.Column(scale=2):
|
| 489 |
-
d_src = gr.Radio(["成员数据(训练集)","非成员数据(测试集)"], value="成员数据(训练集)", label="数据来源")
|
| 490 |
-
d_btn = gr.Button("随机提取样本", variant="primary")
|
| 491 |
d_meta = gr.Markdown()
|
| 492 |
with gr.Column(scale=3):
|
| 493 |
-
d_q = gr.Textbox(label="学生提问", lines=4, interactive=False)
|
| 494 |
-
d_a = gr.Textbox(label="标准回答", lines=4, interactive=False)
|
| 495 |
d_btn.click(cb_sample, [d_src], [d_meta, d_q, d_a])
|
| 496 |
|
| 497 |
-
# ═══════ Tab 3 ═══════
|
| 498 |
-
with gr.Tab("攻击
|
| 499 |
-
gr.Markdown("## 成员推理攻击交互演示\n\n"
|
| 500 |
-
"
|
| 501 |
with gr.Row(equal_height=True):
|
| 502 |
with gr.Column(scale=2):
|
| 503 |
a_target = gr.Radio([u"基线模型 (Baseline)",u"标签平滑 (\u03b5=0.02)",u"标签平滑 (\u03b5=0.2)",
|
| 504 |
u"输出扰动 (\u03c3=0.01)",u"输出扰动 (\u03c3=0.015)",u"输出扰动 (\u03c3=0.02)"],
|
| 505 |
-
value=u"基线模型 (Baseline)", label="攻击目标")
|
| 506 |
a_src = gr.Radio(["成员数据(训练集)","非成员数据(测试集)"], value="成员数据(训练集)", label="数据来源")
|
| 507 |
-
a_idx = gr.Slider(0, 999, step=1, value=12, label="样本 ID")
|
| 508 |
-
a_btn = gr.Button("执行成员推理攻击", variant="primary", size="lg")
|
| 509 |
a_qtxt = gr.Markdown()
|
| 510 |
with gr.Column(scale=3):
|
| 511 |
-
a_gauge = gr.Plot(label="Loss位置判定")
|
| 512 |
a_res = gr.Markdown()
|
| 513 |
a_btn.click(cb_attack, [a_idx, a_src, a_target], [a_qtxt, a_gauge, a_res])
|
| 514 |
|
| 515 |
-
# ═══════ Tab 4 ═══════
|
| 516 |
-
with gr.Tab("防御
|
| 517 |
-
gr.
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
|
|
|
|
|
|
|
|
|
| 554 |
with gr.Row(equal_height=True):
|
| 555 |
with gr.Column(): gr.Plot(value=fig_acc_bar())
|
| 556 |
with gr.Column(): gr.Plot(value=fig_tradeoff())
|
| 557 |
-
|
|
|
|
| 558 |
with gr.Row(equal_height=True):
|
| 559 |
with gr.Column(scale=1):
|
| 560 |
e_model = gr.Radio([u"基线模型",u"标签平滑 (\u03b5=0.02)",u"标签平滑 (\u03b5=0.2)",
|
| 561 |
-
u"输出扰动 (\u03c3=0.01)",u"输出扰动 (\u03c3=0.015)",u"输出扰动 (\u03c3=0.02)"], value=u"基线模型", label="选择模型")
|
| 562 |
-
e_btn = gr.Button("随机抽题测试", variant="primary")
|
| 563 |
with gr.Column(scale=2):
|
| 564 |
e_res = gr.Markdown()
|
| 565 |
e_btn.click(cb_eval, [e_model], [e_res])
|
| 566 |
|
| 567 |
-
# ═══════ Tab 6 ═══════
|
| 568 |
-
with gr.Tab("研究结论"):
|
| 569 |
-
gr.Markdown(
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
"基线模型 AUC = **" + f"{bl_auc:.4f}" + "** > 0.5,成员平均Loss (" + f"{bl_m_mean:.4f}"
|
| 573 |
-
+ ")
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 595 |
|
| 596 |
demo.launch()
|
|
|
|
| 9 |
|
| 10 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 11 |
|
|
|
|
| 12 |
def load_json(path):
|
| 13 |
with open(os.path.join(BASE_DIR, path), 'r', encoding='utf-8') as f:
|
| 14 |
return json.load(f)
|
| 15 |
|
|
|
|
| 16 |
def clean_text(text):
|
| 17 |
if not isinstance(text, str):
|
| 18 |
return str(text)
|
|
|
|
| 21 |
text = re.sub(r'[\u200b-\u200f\u2028-\u202f\u2060-\u206f\ufeff]', '', text)
|
| 22 |
return text.strip()
|
| 23 |
|
|
|
|
| 24 |
member_data = load_json("data/member.json")
|
| 25 |
non_member_data = load_json("data/non_member.json")
|
| 26 |
mia_results = load_json("results/mia_results.json")
|
|
|
|
| 87 |
EVAL_POOL.append({'question':_q,'answer':_ans,'type_cn':TYPE_CN[_t],
|
| 88 |
'baseline':bool(np.random.random()<bl_acc/100),'smooth_0.02':bool(np.random.random()<s002_acc/100),'smooth_0.2':bool(np.random.random()<s02_acc/100)})
|
| 89 |
|
|
|
|
| 90 |
# ══════════════ 图表 ══════════════
|
| 91 |
|
| 92 |
def fig_gauge(loss_val, m_mean, nm_mean, thr, m_std, nm_std):
|
|
|
|
| 108 |
for s in ['top','right','left']: ax.spines[s].set_visible(False)
|
| 109 |
ax.set_xlabel('Loss Value', fontsize=9); plt.tight_layout(); return fig
|
| 110 |
|
|
|
|
| 111 |
def fig_loss_dist():
|
| 112 |
items = [(k,l,mia_results.get(k,{}).get('auc',0)) for k,l in [('baseline','Baseline'),('smooth_0.02',u'LS(\u03b5=0.02)'),('smooth_0.2',u'LS(\u03b5=0.2)')] if k in full_results]
|
| 113 |
n = len(items)
|
|
|
|
| 124 |
ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
|
| 125 |
plt.tight_layout(); return fig
|
| 126 |
|
|
|
|
| 127 |
def fig_perturb_dist():
|
| 128 |
base=full_results.get('baseline',{})
|
| 129 |
if not base: return plt.figure()
|
|
|
|
| 142 |
ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
|
| 143 |
plt.tight_layout(); return fig
|
| 144 |
|
|
|
|
| 145 |
def fig_auc_bar():
|
| 146 |
data=[]
|
| 147 |
for k,n,c in [('baseline','Baseline','#64748b'),(u'smooth_0.02',u'LS(\u03b5=0.02)','#3b82f6'),('smooth_0.2',u'LS(\u03b5=0.2)','#1d4ed8')]:
|
|
|
|
| 156 |
ax.legend(fontsize=9); ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
|
| 157 |
ax.grid(axis='y',alpha=.15); plt.xticks(fontsize=10); plt.tight_layout(); return fig
|
| 158 |
|
|
|
|
| 159 |
def fig_acc_bar():
|
| 160 |
data=[]
|
| 161 |
for k,n,c in [('baseline','Baseline','#64748b'),('smooth_0.02',u'LS(\u03b5=0.02)','#3b82f6'),('smooth_0.2',u'LS(\u03b5=0.2)','#1d4ed8')]:
|
|
|
|
| 170 |
ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
|
| 171 |
ax.grid(axis='y',alpha=.15); plt.xticks(fontsize=10); plt.tight_layout(); return fig
|
| 172 |
|
|
|
|
| 173 |
def fig_tradeoff():
|
| 174 |
fig,ax=plt.subplots(figsize=(9,6.5)); pts=[]
|
| 175 |
for k,n,mk,c in [('baseline','Baseline','o','#64748b'),('smooth_0.02',u'LS(\u03b5=0.02)','s','#3b82f6'),('smooth_0.2',u'LS(\u03b5=0.2)','s','#1d4ed8')]:
|
|
|
|
| 186 |
ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False)
|
| 187 |
plt.tight_layout(); return fig
|
| 188 |
|
|
|
|
| 189 |
# ══════════════ 回调 ══════════════
|
| 190 |
|
| 191 |
def cb_sample(src):
|
| 192 |
pool=member_data if src=="成员数据(训练集)" else non_member_data
|
| 193 |
s=pool[np.random.randint(len(pool))]; m=s['metadata']
|
| 194 |
tm={'calculation':'基础计算','word_problem':'应用题','concept':'概念问答','error_correction':'错题订正'}
|
| 195 |
+
md=("| 字段 | 值 |\n|---|---|\n| 👤 姓名 | "+clean_text(str(m.get('name','')))+
|
| 196 |
+
" |\n| 🆔 学号 | "+clean_text(str(m.get('student_id','')))+
|
| 197 |
+
" |\n| 🏫 班级 | "+clean_text(str(m.get('class','')))+
|
| 198 |
+
" |\n| 💯 成绩 | "+clean_text(str(m.get('score','')))+" 分 |\n| 🔖 类型 | "+tm.get(s.get('task_type',''),'')+" |\n")
|
| 199 |
return md, clean_text(s.get('question','')), clean_text(s.get('answer',''))
|
| 200 |
|
|
|
|
| 201 |
ATK_MAP = {
|
| 202 |
u"基线模型 (Baseline)":"baseline",
|
| 203 |
u"标签平滑 (\u03b5=0.02)":"smooth_0.02",
|
|
|
|
| 207 |
u"输出扰动 (\u03c3=0.02)":"perturbation_0.02",
|
| 208 |
}
|
| 209 |
|
|
|
|
| 210 |
def cb_attack(idx, src, target):
|
| 211 |
is_mem = src=="成员数据(训练集)"
|
| 212 |
pool = member_data if is_mem else non_member_data
|
|
|
|
| 231 |
pl,pc=("训练成员","🔴") if pred else ("非训练成员","🟢")
|
| 232 |
al,ac=("训练成员","🔴") if is_mem else ("非训练成员","🟢")
|
| 233 |
if correct and pred and is_mem:
|
| 234 |
+
v="⚠️ **攻击成功:隐私泄露**\n\n> 模型对该���本过于熟悉(Loss < 阈值),攻击者成功判定为训练数据。"
|
| 235 |
elif correct:
|
| 236 |
+
v="✅ **判定正确**\n\n> 攻击者的判定与真实身份一致。"
|
| 237 |
else:
|
| 238 |
+
v="🛡️ **防御成功**\n\n> 攻击者的判定错误,防御起到了保护作用。"
|
| 239 |
+
res=(v+"\n\n**🎯 攻击目标**: "+lbl+" | **📊 AUC**: "+f"{auc_v:.4f}"+"\n\n"
|
| 240 |
"| | 攻击者判定 | 真实身份 |\n|---|---|---|\n"
|
| 241 |
"| 身份 | "+pc+" "+pl+" | "+ac+" "+al+" |\n"
|
| 242 |
"| Loss | "+f"{loss:.4f}"+" | 阈值: "+f"{thr:.4f}"+" |\n")
|
| 243 |
+
qtxt="**📝 样本题号 #"+str(idx)+"**\n\n"+clean_text(sample.get('question',''))[:500]
|
| 244 |
return qtxt, gauge, res
|
| 245 |
|
|
|
|
| 246 |
EVAL_ACC={u"基线模型":bl_acc,u"标签平滑 (\u03b5=0.02)":s002_acc,u"标签平滑 (\u03b5=0.2)":s02_acc,
|
| 247 |
u"输出扰动 (\u03c3=0.01)":bl_acc,u"输出扰动 (\u03c3=0.015)":bl_acc,u"输出扰动 (\u03c3=0.02)":bl_acc}
|
| 248 |
EVAL_KEY={u"基线模型":"baseline",u"标签平滑 (\u03b5=0.02)":"smooth_0.02",u"标签平滑 (\u03b5=0.2)":"smooth_0.2",
|
| 249 |
u"输出扰动 (\u03c3=0.01)":"baseline",u"输出扰动 (\u03c3=0.015)":"baseline",u"输出扰动 (\u03c3=0.02)":"baseline"}
|
| 250 |
|
|
|
|
| 251 |
def cb_eval(model):
|
| 252 |
k=EVAL_KEY.get(model,"baseline"); acc=EVAL_ACC.get(model,bl_acc)
|
| 253 |
q=EVAL_POOL[np.random.randint(len(EVAL_POOL))]; ok=q.get(k,q.get('baseline',False))
|
| 254 |
ic="✅ 正确" if ok else "❌ 错误"
|
| 255 |
+
note="\n\n> 💡 输出扰动不改变模型参数,准确率与基线一致。" if u"\u03c3" in model else ""
|
| 256 |
+
return ("**🖥️ 模型**: "+model+" (准确率: "+f"{acc:.1f}%"+")\n\n"
|
| 257 |
"| 项目 | 内容 |\n|---|---|\n"
|
| 258 |
"| 类型 | "+q['type_cn']+" |\n| 题目 | "+q['question']+" |\n"
|
| 259 |
"| 正确答案 | "+q['answer']+" |\n| 判定 | "+ic+" |"+note)
|
| 260 |
|
| 261 |
|
| 262 |
+
# ══════════════ 界面美化 CSS ══════════════
|
| 263 |
|
| 264 |
CSS = """
|
| 265 |
+
/* 1. 带有十字准星网格的全局背景 */
|
| 266 |
body {
|
| 267 |
+
background-color: #f8fafc !important;
|
| 268 |
+
background-image:
|
| 269 |
+
linear-gradient(#e2e8f0 1px, transparent 1px),
|
| 270 |
+
linear-gradient(90deg, #e2e8f0 1px, transparent 1px) !important;
|
| 271 |
+
background-size: 20px 20px !important;
|
| 272 |
+
background-position: center center !important;
|
| 273 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important;
|
| 274 |
}
|
| 275 |
.gradio-container {
|
|
|
|
| 277 |
margin: 40px auto !important;
|
| 278 |
}
|
| 279 |
|
| 280 |
+
/* 2. 科技感悬浮 Title 面板 */
|
| 281 |
.title-area {
|
| 282 |
background: #ffffff;
|
| 283 |
+
padding: 28px 40px;
|
| 284 |
border-radius: 12px;
|
| 285 |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
| 286 |
+
margin-bottom: 24px;
|
| 287 |
+
border-left: 6px solid #2563eb;
|
| 288 |
text-align: left;
|
| 289 |
}
|
| 290 |
.title-area h1 {
|
|
|
|
| 301 |
font-weight: 500;
|
| 302 |
}
|
| 303 |
|
| 304 |
+
/* 3. 死死锁住所有标签页的大小,防止跳动 (760px 高度) */
|
| 305 |
.tabitem {
|
| 306 |
+
background: rgba(255, 255, 255, 0.98) !important;
|
| 307 |
border-radius: 0 0 12px 12px !important;
|
| 308 |
border: 1px solid #e2e8f0 !important;
|
| 309 |
border-top: none !important;
|
| 310 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05) !important;
|
| 311 |
padding: 32px 40px !important;
|
| 312 |
|
|
|
|
| 313 |
height: 760px !important;
|
| 314 |
max-height: 760px !important;
|
| 315 |
overflow-y: auto !important;
|
| 316 |
overflow-x: hidden !important;
|
| 317 |
}
|
| 318 |
|
| 319 |
+
/* 优雅的隐藏式滚动条 */
|
| 320 |
.tabitem::-webkit-scrollbar { width: 6px; }
|
| 321 |
.tabitem::-webkit-scrollbar-track { background: transparent; }
|
| 322 |
.tabitem::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
|
| 323 |
.tabitem::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
| 324 |
|
| 325 |
+
/* 4. Tab 导航栏拟态设计 */
|
| 326 |
.tab-nav {
|
| 327 |
border-bottom: none !important;
|
| 328 |
gap: 4px !important;
|
|
|
|
| 333 |
padding: 12px 24px !important;
|
| 334 |
font-weight: 600 !important;
|
| 335 |
color: #64748b !important;
|
| 336 |
+
background: #e2e8f0 !important;
|
| 337 |
border: 1px solid #e2e8f0 !important;
|
| 338 |
border-bottom: none !important;
|
| 339 |
border-radius: 10px 10px 0 0 !important;
|
|
|
|
| 351 |
box-shadow: 0 -4px 6px -2px rgba(0,0,0,0.02) !important;
|
| 352 |
}
|
| 353 |
|
| 354 |
+
/* 5. 内部排版优化 */
|
| 355 |
.prose h2 {
|
| 356 |
font-size: 1.3rem !important;
|
| 357 |
color: #0f172a !important;
|
|
|
|
| 394 |
.prose tr:last-child td { border-bottom: none !important; }
|
| 395 |
.prose tr:hover td { background: #f0f9ff !important; }
|
| 396 |
|
| 397 |
+
/* 7. 纯色科技感按钮 */
|
| 398 |
button.primary {
|
| 399 |
background: #2563eb !important;
|
| 400 |
color: white !important;
|
|
|
|
| 411 |
box-shadow: 0 6px 10px -1px rgba(37, 99, 235, 0.3) !important;
|
| 412 |
}
|
| 413 |
|
| 414 |
+
/* 8. 提示框 */
|
| 415 |
.prose blockquote {
|
| 416 |
border-left: 4px solid #3b82f6 !important;
|
| 417 |
background: #eff6ff !important;
|
|
|
|
| 422 |
margin: 1.5em 0 !important;
|
| 423 |
}
|
| 424 |
|
| 425 |
+
/* 9. Accordion 折叠面板美化 */
|
| 426 |
+
.wrap.svelte-182y6v9 {
|
| 427 |
+
border: 1px solid #e2e8f0 !important;
|
| 428 |
+
border-radius: 8px !important;
|
| 429 |
+
background: #f8fafc !important;
|
| 430 |
+
box-shadow: none !important;
|
| 431 |
+
}
|
| 432 |
+
.label.svelte-182y6v9 {
|
| 433 |
+
font-weight: 600 !important;
|
| 434 |
+
color: #0f172a !important;
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
footer { display: none !important; }
|
| 438 |
"""
|
| 439 |
|
| 440 |
with gr.Blocks(title="MIA攻防研究", theme=gr.themes.Base(), css=CSS) as demo:
|
| 441 |
|
| 442 |
gr.HTML("""<div class="title-area">
|
| 443 |
+
<h1>🎓 教育大模型中的成员推理攻击及其防御研究</h1>
|
| 444 |
+
<p>Membership Inference Attack & Defense on Educational LLM Dashboard</p>
|
| 445 |
</div>""")
|
| 446 |
|
| 447 |
+
# ═══════ Tab 1: 实验总览 (引入折叠面板展开) ═══════
|
| 448 |
+
with gr.Tab("📊 实验总览"):
|
| 449 |
+
gr.Markdown("## 📌 研究背景与目标\n\n大语言模型在教育领域的应用日益广泛(如AI数学辅导),模型训练不可避免地接触学生敏感数据。**成员推理攻击 (MIA)** 可判断某条数据是否参与了训练,构成隐私威胁。\n\n本研究基于 **" + model_name + "** 微调的数学辅导模型,验证MIA风险的存在性,并探索 **标签平滑**(训练期)与 **输出扰动**(推理期)两类防御策略的有效性及其对模型效用的影响。")
|
| 450 |
+
|
| 451 |
+
with gr.Accordion("📈 展开查看:实验核心指标", open=True):
|
| 452 |
+
gr.Markdown(
|
| 453 |
+
"| 🛡️ 策略配置 | 📊 AUC | 🎯 准确率 | 💡 说明 |\n|---|---|---|---|\n"
|
| 454 |
+
"| **基线(无防御)** | **" + f"{bl_auc:.4f}" + "** | " + f"{bl_acc:.1f}%" + " | 攻击风险基准 |\n"
|
| 455 |
+
"| " + u"LS(\u03b5=0.02)" + " | " + f"{s002_auc:.4f}" + " | " + f"{s002_acc:.1f}%" + " | 训练期防御 |\n"
|
| 456 |
+
"| " + u"LS(\u03b5=0.2)" + " | " + f"{s02_auc:.4f}" + " | " + f"{s02_acc:.1f}%" + " | 训练期防御 |\n"
|
| 457 |
+
"| " + u"OP(\u03c3=0.01)" + " | " + f"{op001_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " | 推理期防御 |\n"
|
| 458 |
+
"| " + u"OP(\u03c3=0.015)" + " | " + f"{op0015_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " | 推理期防御 |\n"
|
| 459 |
+
"| " + u"OP(\u03c3=0.02)" + " | " + f"{op002_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " | 推理期防御 |\n\n"
|
| 460 |
+
"> 💡 **指标提示**: AUC越接近0.5 = 防御越有效;准确率越高 = 模型效用越好。"
|
| 461 |
+
)
|
| 462 |
+
|
| 463 |
+
with gr.Accordion("🚀 展开查看:实验流程规划", open=False):
|
| 464 |
+
gr.Markdown(
|
| 465 |
+
"| 阶段 | 内容 | 方法 |\n|---|---|---|\n"
|
| 466 |
+
"| 1. 数据准备 | 2000条数学辅导对话 | 模板化生成,含姓名/学号/成绩 |\n"
|
| 467 |
+
"| 2. 基线训练 | " + model_name + " + LoRA | 标准微调(r=8, alpha=16, 10 epochs) |\n"
|
| 468 |
+
"| 3. 防御训练 | " + u"\u03b5=0.02 / \u03b5=0.2" + " | 两组标签平滑参数分别训练 |\n"
|
| 469 |
+
"| 4. 攻击测试 | 3个模型 + 3组扰动 | Loss阈值判定,AUC评估 |\n"
|
| 470 |
+
"| 5. 效用评估 | 300道数学题 | 6种配置分别测试准确率 |\n"
|
| 471 |
+
"| 6. 综合分析 | 隐私-效用权衡 | 定量对比与可视化 |\n"
|
| 472 |
+
)
|
| 473 |
+
|
| 474 |
+
# ═══════ Tab 2: 数据与模型 ═══════
|
| 475 |
+
with gr.Tab("📁 数据与模型"):
|
| 476 |
+
gr.Markdown("## 📦 实验数据集概况")
|
| 477 |
+
|
| 478 |
+
with gr.Row():
|
| 479 |
+
with gr.Column(scale=1):
|
| 480 |
+
gr.Markdown(
|
| 481 |
+
"| 数据组 | 数量 | 用途 | 说明 |\n|---|---|---|---|\n"
|
| 482 |
+
"| 🔴 成员数据 | 1000条 | 模型训练 | 模型会\"记住\",Loss偏低 |\n"
|
| 483 |
+
"| 🟢 非成员数据 | 1000条 | 攻击对照 | 模型\"没见过\",Loss偏高 |\n\n"
|
| 484 |
+
"> ⚠️ 两组数据格式完全相同(均含隐私字段),这是MIA实验的标准设置——攻击者无法从格式区分。"
|
| 485 |
+
)
|
| 486 |
+
with gr.Column(scale=1):
|
| 487 |
+
gr.Markdown(
|
| 488 |
+
"| 任务类别 | 数量 | 占比 |\n|---|---|---|\n"
|
| 489 |
+
"| 🧮 基础计算 | 800 | 40% |\n| 📝 应用题 | 600 | 30% |\n| 🧠 概念问答 | 400 | 20% |\n| ✍️ 错题订正 | 200 | 10% |\n"
|
| 490 |
+
)
|
| 491 |
+
|
| 492 |
+
gr.Markdown("### 🔍 数据样例浏览提取")
|
| 493 |
with gr.Row(equal_height=True):
|
| 494 |
with gr.Column(scale=2):
|
| 495 |
+
d_src = gr.Radio(["成员数据(训练集)","非成员数据(测试集)"], value="成员数据(训练集)", label="选择靶向数据来源")
|
| 496 |
+
d_btn = gr.Button("🎲 随机提取样本", variant="primary")
|
| 497 |
d_meta = gr.Markdown()
|
| 498 |
with gr.Column(scale=3):
|
| 499 |
+
d_q = gr.Textbox(label="🧑🎓 学生提问 (Prompt)", lines=4, interactive=False)
|
| 500 |
+
d_a = gr.Textbox(label="🤖 标准回答 (Ground Truth)", lines=4, interactive=False)
|
| 501 |
d_btn.click(cb_sample, [d_src], [d_meta, d_q, d_a])
|
| 502 |
|
| 503 |
+
# ═══════ Tab 3: 攻击与防御验证 ═══════
|
| 504 |
+
with gr.Tab("🎯 攻击验证"):
|
| 505 |
+
gr.Markdown("## 🕵️ 成员推理攻击交互演示\n\n"
|
| 506 |
+
"配置攻击目标实体与数据源,系统将执行 Loss 计算并映射攻击边界,以此判定数据归属。")
|
| 507 |
with gr.Row(equal_height=True):
|
| 508 |
with gr.Column(scale=2):
|
| 509 |
a_target = gr.Radio([u"基线模型 (Baseline)",u"标签平滑 (\u03b5=0.02)",u"标签平滑 (\u03b5=0.2)",
|
| 510 |
u"输出扰动 (\u03c3=0.01)",u"输出扰动 (\u03c3=0.015)",u"输出扰动 (\u03c3=0.02)"],
|
| 511 |
+
value=u"基线模型 (Baseline)", label="选择攻击目标")
|
| 512 |
a_src = gr.Radio(["成员数据(训练集)","非成员数据(测试集)"], value="成员数据(训练集)", label="数据来源")
|
| 513 |
+
a_idx = gr.Slider(0, 999, step=1, value=12, label="定位样本 ID")
|
| 514 |
+
a_btn = gr.Button("⚡ 执行成员推理攻击", variant="primary", size="lg")
|
| 515 |
a_qtxt = gr.Markdown()
|
| 516 |
with gr.Column(scale=3):
|
| 517 |
+
a_gauge = gr.Plot(label="Loss位置判定 (Decision Boundary)")
|
| 518 |
a_res = gr.Markdown()
|
| 519 |
a_btn.click(cb_attack, [a_idx, a_src, a_target], [a_qtxt, a_gauge, a_res])
|
| 520 |
|
| 521 |
+
# ═══════ Tab 4: 防御效果分析 ═══════
|
| 522 |
+
with gr.Tab("🛡️ 防御分析"):
|
| 523 |
+
with gr.Accordion("📊 展开查看:防御对比直方图", open=False):
|
| 524 |
+
gr.Markdown("### MIA攻击AUC对比\n\n> 柱子��矮 = AUC越低 = 攻击越难成功 = 防御越有效")
|
| 525 |
+
gr.Plot(value=fig_auc_bar())
|
| 526 |
+
|
| 527 |
+
gr.Markdown("### Loss分布对比\n#### 三个模型(训练期防御效果)\n\n> 蓝色=成员,红色=非成员。两色重叠越多 = 攻击者越难区分")
|
| 528 |
+
gr.Plot(value=fig_loss_dist())
|
| 529 |
+
gr.Markdown("#### 输出扰动效果(推理期防御)\n\n> 在基线模型Loss上加噪声,随噪声增大分布更加重叠")
|
| 530 |
+
gr.Plot(value=fig_perturb_dist())
|
| 531 |
+
|
| 532 |
+
with gr.Accordion("⚙️ 展开查看:完整实验数据与机制说明", open=True):
|
| 533 |
+
gr.Markdown(
|
| 534 |
+
"### 完整实验数据表\n\n"
|
| 535 |
+
"| 策略 | 类型 | AUC | 准确率 | AUC变化 |\n|---|---|---|---|---|\n"
|
| 536 |
+
"| 基线 | — | " + f"{bl_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " | — |\n"
|
| 537 |
+
"| " + u"LS(\u03b5=0.02)" + " | 训练期 | " + f"{s002_auc:.4f}" + " | " + f"{s002_acc:.1f}%" + " | " + f"{s002_auc-bl_auc:+.4f}" + " |\n"
|
| 538 |
+
"| " + u"LS(\u03b5=0.2)" + " | 训练期 | " + f"{s02_auc:.4f}" + " | " + f"{s02_acc:.1f}%" + " | " + f"{s02_auc-bl_auc:+.4f}" + " |\n"
|
| 539 |
+
"| " + u"OP(\u03c3=0.01)" + " | 推理期 | " + f"{op001_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " | " + f"{op001_auc-bl_auc:+.4f}" + " |\n"
|
| 540 |
+
"| " + u"OP(\u03c3=0.015)" + " | 推理期 | " + f"{op0015_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " | " + f"{op0015_auc-bl_auc:+.4f}" + " |\n"
|
| 541 |
+
"| " + u"OP(\u03c3=0.02)" + " | 推理期 | " + f"{op002_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " | " + f"{op002_auc-bl_auc:+.4f}" + " |\n\n"
|
| 542 |
+
"### 防御机制对比\n\n"
|
| 543 |
+
"| 维度 | 标签平滑 | 输出扰动 |\n|---|---|---|\n"
|
| 544 |
+
"| **阶段** | 训练期 | 推理期 |\n"
|
| 545 |
+
"| **原理** | 软化标签降低记忆 | Loss加噪遮蔽信号 |\n"
|
| 546 |
+
"| **需重训** | 是 | 否 |\n"
|
| 547 |
+
"| **效用** | 取决于参数 | 无 |\n"
|
| 548 |
+
"| **部署** | 训练时介入 | 即插即用 |\n\n"
|
| 549 |
+
"**标签平滑公式**: `y_smooth = (1 - ε) * y_onehot + ε / V`\n\n"
|
| 550 |
+
"**输出扰动公式**: `L_perturbed = L_original + N(0, σ²)`\n")
|
| 551 |
+
|
| 552 |
+
with gr.Accordion("🖼️ 展开查看:静态高分辨率学术图表", open=False):
|
| 553 |
+
for fn, cap in [("fig1_loss_distribution_comparison.png","Loss分布对比"),
|
| 554 |
+
("fig2_privacy_utility_tradeoff_fixed.png","隐私-效用权衡"),
|
| 555 |
+
("fig3_defense_comparison_bar.png","防御策略AUC对比")]:
|
| 556 |
+
p = os.path.join(BASE_DIR,"figures",fn)
|
| 557 |
+
if os.path.exists(p):
|
| 558 |
+
gr.Markdown("#### "+cap); gr.Image(value=p, show_label=False, height=420)
|
| 559 |
+
|
| 560 |
+
# ═══════ Tab 5: 效用评估 ═══════
|
| 561 |
+
with gr.Tab("⚖️ 效用评估"):
|
| 562 |
+
gr.Markdown("## 🎯 模型效用测试\n\n> 基于300道数学测试题评估各策略对模型实际能力的影响")
|
| 563 |
with gr.Row(equal_height=True):
|
| 564 |
with gr.Column(): gr.Plot(value=fig_acc_bar())
|
| 565 |
with gr.Column(): gr.Plot(value=fig_tradeoff())
|
| 566 |
+
|
| 567 |
+
gr.Markdown("## 🎮 在线效用抽样演示\n\n从测试题库中随机抽取,流式验证不同模型/策略的保留作答情况。")
|
| 568 |
with gr.Row(equal_height=True):
|
| 569 |
with gr.Column(scale=1):
|
| 570 |
e_model = gr.Radio([u"基线模型",u"标签平滑 (\u03b5=0.02)",u"标签平滑 (\u03b5=0.2)",
|
| 571 |
+
u"输出扰动 (\u03c3=0.01)",u"输出扰动 (\u03c3=0.015)",u"输出扰动 (\u03c3=0.02)"], value=u"基线模型", label="选择验证模型")
|
| 572 |
+
e_btn = gr.Button("🧪 随机抽题测试", variant="primary")
|
| 573 |
with gr.Column(scale=2):
|
| 574 |
e_res = gr.Markdown()
|
| 575 |
e_btn.click(cb_eval, [e_model], [e_res])
|
| 576 |
|
| 577 |
+
# ═══════ Tab 6: 研究结论 (引入折叠面板展开) ═══════
|
| 578 |
+
with gr.Tab("📝 研究结论"):
|
| 579 |
+
gr.Markdown("## 💡 核心研究发现与最佳实践\n\n---")
|
| 580 |
+
|
| 581 |
+
with gr.Accordion("🚨 一、教育大模型存在可量化的MIA风险", open=True):
|
| 582 |
+
gr.Markdown("基线模型 AUC = **" + f"{bl_auc:.4f}" + "** > 0.5,成员平均Loss (" + f"{bl_m_mean:.4f}"
|
| 583 |
+
+ ") 显著小于 非成员 (" + f"{bl_nm_mean:.4f}" + "),实验铁证表明模型对训练数据存在可利用的记忆效应。")
|
| 584 |
+
|
| 585 |
+
with gr.Accordion("🛡️ 二、标签平滑(训练期防御)有效性验证", open=True):
|
| 586 |
+
gr.Markdown(
|
| 587 |
+
"| 参数 | AUC | 准确率 | 分析 |\n|---|---|---|---|\n"
|
| 588 |
+
"| 基线 | " + f"{bl_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " | 无防御 |\n"
|
| 589 |
+
"| " + u"\u03b5=0.02" + " | " + f"{s002_auc:.4f}" + " | " + f"{s002_acc:.1f}%" + " | 正则化提升泛化 |\n"
|
| 590 |
+
"| " + u"\u03b5=0.2" + " | " + f"{s02_auc:.4f}" + " | " + f"{s02_acc:.1f}%" + " | 防御增强 |\n"
|
| 591 |
+
)
|
| 592 |
+
|
| 593 |
+
with gr.Accordion("🎛️ 三、输出扰动(推理期防御)有效性验证", open=True):
|
| 594 |
+
gr.Markdown(
|
| 595 |
+
"| 参数 | AUC | AUC降幅 | 准确率 |\n|---|---|---|---|\n"
|
| 596 |
+
"| " + u"\u03c3=0.01" + " | " + f"{op001_auc:.4f}" + " | " + f"{bl_auc-op001_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " |\n"
|
| 597 |
+
"| " + u"\u03c3=0.015" + " | " + f"{op0015_auc:.4f}" + " | " + f"{bl_auc-op0015_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " |\n"
|
| 598 |
+
"| " + u"\u03c3=0.02" + " | " + f"{op002_auc:.4f}" + " | " + f"{bl_auc-op002_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " |\n\n"
|
| 599 |
+
"**结论:零效用损失,适合已部署系统的后期加固。**"
|
| 600 |
+
)
|
| 601 |
+
|
| 602 |
+
with gr.Accordion("⚖️ 四、隐私-效用权衡总结", open=False):
|
| 603 |
+
gr.Markdown(
|
| 604 |
+
"| 策略 | AUC | 准确率 | 隐私 | 效用 |\n|---|---|---|---|---|\n"
|
| 605 |
+
"| 基线 | " + f"{bl_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " | 风险最高 | 基准 |\n"
|
| 606 |
+
"| " + u"LS(\u03b5=0.02)" + " | " + f"{s002_auc:.4f}" + " | " + f"{s002_acc:.1f}%" + " | 降低 | 提升 |\n"
|
| 607 |
+
"| " + u"LS(\u03b5=0.2)" + " | " + f"{s02_auc:.4f}" + " | " + f"{s02_acc:.1f}%" + " | 显著降低 | 可接受 |\n"
|
| 608 |
+
"| " + u"OP(\u03c3=0.02)" + " | " + f"{op002_auc:.4f}" + " | " + f"{bl_acc:.1f}%" + " | 显著降低 | 不变 |\n\n"
|
| 609 |
+
"> 两类策略机制互补:标签平滑从训练阶段降低记忆,输出扰动从推理阶段遮蔽信号。建议组合使用以构建立体防御体系。"
|
| 610 |
+
)
|
| 611 |
+
|
| 612 |
+
gr.HTML("<div style='text-align:center;color:#94a3b8;font-size:.82rem;padding:16px 0 8px'></div>")
|
| 613 |
|
| 614 |
demo.launch()
|