
簡介一套面向高校學生成績預測的聯邦學習Python實現專注于隱私保護下的分布式模型訓練適合課程設計、畢業設計、科研入門或教學演示。資源包共30個文件以18個Python腳本為核心配套7個CSV數據與實驗結果文件以及說明文檔和配置文件壓縮包僅2.18MB。已有31人學習下載。系統內置FedRep、SCAFFOLD、Ditto、APFL、L2GD、MTL、FedProx及本地訓練等多種算法支持多客戶端模擬并提供Streamlit交互式可視化界面可實時查看混淆矩陣、訓練曲線與預測結果。所有代碼基于PyTorch構建包含完整訓練測試流程、網絡定義、數據采樣與通信輔助模塊配套真實學生成績數據集和MNIST風格模擬實驗記錄便于復現與橫向對比。項目已完整運行驗證支持直接執行main_xxx.py啟動對應算法無需深度調參即可觀察不同算法在成績預測任務上的收斂性與泛化表現。 做了這個項目之后我對聯邦學習這四個字的理解才算真正落地。以前看論文總覺得它是個很玄的東西直到自己動手把高校學生成績預測這個場景完整實現了一遍——用Python寫聯邦訓練邏輯、用Streamlit做可視化界面、再跑多輪多算法對比實驗——才發現這個方向最大的價值不是算法多花哨而是它解決了一個非常實際的問題不同學院、不同高校之間的成績數據能不能在不直接共享原始數據的前提下一起訓練一個更高精度的預測模型。這篇內容我會從需求拆解、算法選型、核心代碼實現、Streamlit界面搭建到實驗結果分析完整還原整個項目過程。適合正在做聯邦學習相關課程設計、畢業設計或者想快速上手聯邦學習可視化開發的讀者參考里面的代碼思路和踩坑記錄都可以直接拿過去用。1. 項目定位與核心需求拆解1.1 成績預測場景里的數據孤島現象高校學生成績預測這個任務本身不算新鮮用學生的出勤率、作業完成情況、歷史成績、一卡通消費記錄這些特征去預測期末是否掛科是教育數據挖掘里非常經典的分類問題。但真正做起來會發現一個繞不開的障礙成績數據往往分散在不同學院甚至不同學校的信息系統里每個單位的數據量有限特征分布也不一樣。比如計算機學院的學生編程類課程成績普遍偏高外國語學院的成績分布又完全是另一套邏輯。如果每個學院各訓各的模型數據少、特征單一模型泛化能力很差。但如果把所有學院的原始成績數據集中到一個服務器上訓練又涉及學生隱私、數據所有權、跨部門協調這些敏感問題——現實中基本走不通。這時候聯邦學習就派上了用場各個參與方客戶端在自己的本地用自有數據訓練模型只把模型參數梯度、權重上傳給中心服務器服務器完成聚合后再把更新后的全局模型下發回各個客戶端。整個過程原始數據不出本地從機制上繞開了數據合規和隱私爭議。1.2 聯邦學習選型的兩個關鍵理由第一隱私保護不是錦上添花而是這個場景的硬性約束。學生成績屬于個人敏感信息任何高校都不可能授權你把數據打包帶走做集中訓練。聯邦學習的數據不動模型動特性讓多方協作訓練在制度層面變得可行。第二小樣本學院能從全局模型中受益。有些冷門學院一屆學生可能只有幾百人單靠本地數據訓練模型很容易過擬合。通過聯邦學習參與協作這類客戶端可以拿到全局模型——這個模型融合了所有參與方的知識——再結合本地數據做微調預測效果會比孤立訓練好不少。1.3 項目技術棧與總體架構這個項目我用的技術棧如下Python 3.9深度學習框架用PyTorch 1.13機器學習算法用scikit-learn和XGBoost聯邦聚合算法自己實現FedAvg和FedYogi不依賴現成的聯邦框架方便看清內部邏輯可視化界面用Streamlit純Python開發不用寫前端代碼適合快速搭建數據應用數據集用UCI的Student Performance數據集包含數學和葡萄牙語兩門課的成績手動模擬劃分到多個客戶端整體架構分三層數據層各客戶端本地持有的Non-IID數據、聯邦訓練層本地訓練參數上傳服務端聚合、展示層Streamlit讀取訓練日志和指標做可視化。2. 聯邦學習框架與算法選型2.1 FedAvg和FedYogi的核心差異FedAvg聯邦平均是最基礎的聚合算法思路非常直白服務端收到各客戶端上傳的模型權重后按照各客戶端的數據量占比做加權平均得到新的全局模型。公式可以簡化為w_global Σ (n_k / n_total) * w_k其中n_k是第k個客戶端本地樣本數n_total是所有客戶端樣本總數。這個算法實現簡單在數據分布近似獨立同分布IID時表現很好。但一旦數據變成Non-IID——比如不同客戶端覆蓋了完全不同的成績區間——FedAvg收斂就會變慢嚴重時甚至發散。FedYogi則借鑒了自適應優化器Yogi的思路服務端聚合時不再簡單加權平均而是給每個參數維度維護一個自適應學習率。Yogi的更新規則可以理解為在Adam基礎上加了更穩定的二階矩估計減少訓練初期的學習率震蕩。具體更新邏輯是delta_w w_global_t - w_global_{t-1} v_t v_{t-1} - (1 - beta2) * sign(v_{t-1} - delta_w^2) * delta_w^2 w_global_{t1} w_global_t - lr * delta_w / (sqrt(v_t) epsilon)我用一個類比來解釋FedAvg相當于全班同學各自復習后老師把所有同學的水平平均一下得到一個標準版本FedYogi則是在平均的基礎上對不同科目參數維度動態調整復習強度薄弱的科目多花力氣。在Non-IID場景下FedYogi對分布偏斜的魯棒性明顯更強。2.2 基線算法設定光有聯邦模型還不行為了說明聯邦學習的價值我設計了三組基線做對照第一組是完全本地訓練每個客戶端只用自己的小數據訓練模型不參與任何協作。這代表了數據孤島現狀下的最差水平。第二組是中心化訓練Oracle把所有人的數據集中起來訓練一個模型。這代表理論上限——現實中因為隱私約束做不到但作為性能上界很有參考意義。第三組是聯邦訓練分別用FedAvg和FedYogi聚合模擬真實可用方案。2.3 評估指標設計成績預測本質是二分類問題是否掛科但樣本類不平衡問題比較明顯——不掛科的學生通常占80%以上。只看準確率容易被蒙對掩蓋問題所以我把重點放在F1分數和AUC上F1兼顧精確率和召回率AUC反映模型區分正負樣本的能力。同時記錄每輪聯邦通信的輪次和收斂時間評估通信效率。3. Python核心實現與關鍵代碼3.1 用Dirichlet分布模擬Non-IID數據現實中不同學院的數據分布差異非常大為了模擬這種場景我用Dirichlet分布來控制每個客戶端上的類別分布偏移。Dirichlet分布的濃度參數alpha越小各客戶端的數據分布差異越大。alpha取值0.1時極端情況下某些客戶端可能幾乎全是不掛科樣本。import numpy as np from sklearn.model_selection import train_test_split def split_non_iid(labels, num_clients, alpha0.5, seed42): np.random.seed(seed) n_classes len(np.unique(labels)) client_indices [[] for _ in range(num_clients)] # 為每個類別分別劃分 for cls in range(n_classes): idx_cls np.where(labels cls)[0] # Dirichlet分布生成每個客戶端該類的比例 proportions np.random.dirichlet([alpha] * num_clients) # 按比例分配索引 assigned 0 for cid in range(num_clients): n_assign int(round(len(idx_cls) * proportions[cid])) if cid num_clients - 1: n_assign len(idx_cls) - assigned client_indices[cid].extend(idx_cls[assigned: assigned n_assign]) assigned n_assign return [np.array(indices, dtypeint) for indices in client_indices]這塊有個容易踩的坑最后寫索引時如果不顯式處理round帶來的余數會丟樣本或者索引越界。我加了最后一個客戶端兜底邏輯保證所有樣本都被分出去。當你把alpha分別設為0.1、0.5、1.0跑一遍就能直觀看到數據分布從極端偏斜到接近均勻的變化。3.2 本地客戶端訓練流程每個客戶端維護一個本地的PyTorch模型訓練時只用自己的劃分數據做幾輪SGD然后上傳梯度或權重。為了模擬真實場景客戶端之間不會共享任何原始數據。import torch import torch.nn as nn import torch.optim as optim class LocalClient: def __init__(self, client_id, train_data, train_labels, lr0.01, local_epochs3): self.client_id client_id self.train_data torch.tensor(train_data, dtypetorch.float32) self.train_labels torch.tensor(train_labels, dtypetorch.long) self.lr lr self.local_epochs local_epochs self.model None def train_one_round(self, global_model): # 用全局模型參數初始化本地模型 self.model copy.deepcopy(global_model) optimizer optim.SGD(self.model.parameters(), lrself.lr) loss_fn nn.CrossEntropyLoss() self.model.train() for epoch in range(self.local_epochs): optimizer.zero_grad() outputs self.model(self.train_data) loss loss_fn(outputs, self.train_labels) loss.backward() optimizer.step() # 返回本地模型參數 return {name: param.clone() for name, param in self.model.state_dict().items()}local_epochs這個參數值得單獨說。設太大會導致客戶端過度自信地在本地數據上過擬合上傳的模型偏離全局最優設太小又學不到位聚合效果差。我在實驗中固定為3輪再配合早停控制整體通信輪次。3.3 服務端聚合邏輯服務端聚合是核心中的核心我同時實現了FedAvg和FedYogi兩種聚合邏輯用同一個接口切換。class FedServer: def __init__(self, global_model, aggregationfedavg, lr0.01, beta20.999): self.global_model global_model self.aggregation aggregation self.lr lr self.beta2 beta2 self.v None # FedYogi需要的二階矩估計 def aggregate(self, client_weights, client_sizes): total_size sum(client_sizes) # 按數據量加權初始化聚合結果 w_avg {} with torch.no_grad(): for key in self.global_model.state_dict(): w_avg[key] torch.zeros_like( self.global_model.state_dict()[key] ) for w, size in zip(client_weights, client_sizes): w_avg[key] (size / total_size) * w[key] if self.aggregation fedavg: # 直接更新 self.global_model.load_state_dict(w_avg) elif self.aggregation fedyogi: with torch.no_grad(): if self.v is None: self.v {} for key in w_avg: self.v[key] torch.zeros_like(w_avg[key]) # 計算和上一輪全局模型的差值 for key in w_avg: delta w_avg[key] - self.global_model.state_dict()[key] self.v[key] self.v[key] - \ (1 - self.beta2) * torch.sign( self.v[key] - delta * delta ) * (delta * delta) # 自適應更新 w_avg[key] self.global_model.state_dict()[key] - \ self.lr * delta / (torch.sqrt(self.v[key]) 1e-6) self.global_model.load_state_dict(w_avg) return self.global_model.state_dict()FedYogi實現里最需要注意的就是v的初始化第一輪時v是零向量這時候delta / sqrt(v epsilon)中的epsilon如果太小比如1e-8步長會非常大容易爆炸。我把epsilon放寬到1e-6同時lr設小一些實際跑下來穩定很多。3.4 多算法對比實驗封裝為了運行對比實驗我封裝了一個統一的評估入口。本地模型用邏輯回歸、決策樹、隨機森林、XGBoost聯邦模型用FedAvg和FedYogi統一用相同的數據劃分和評估指標。def run_experiment(dataset, alpha, model_type, aggregationNone): # 1. 劃分Non-IID客戶端數據 clients_data split_non_iid(dataset.labels, num_clients5, alphaalpha) # 2. 訓練 if model_type in [logistic, dt, rf, xgb]: # 本地訓練或集中訓練 model train_local_model(dataset, model_type) elif model_type in [fedavg, fedyogi]: server FedServer(init_model(), aggregationmodel_type) for round in range(communication_rounds): client_weights [] client_sizes [] for client in clients: w client.train_one_round(server.global_model) client_weights.append(w) client_sizes.append(len(client.train_data)) server.aggregate(client_weights, client_sizes) model server.global_model # 3. 評估 metrics evaluate(model, dataset.test_data, dataset.test_labels) return metrics這里有個設計取舍本地模型邏輯回歸、決策樹等拿到的是劃分后某個客戶端的數據模擬只用自己數據的效果而對比實驗的目的就是看聯邦模型能不能通過協作超過這些單打獨斗的本地模型。4. Streamlit可視化界面構建4.1 頁面布局與交互設計Streamlit做這種數據展示界面確實省心——不用寫一行前端代碼就能做出帶側邊欄、指標卡片、交互圖表的儀表盤。我做了一個單頁應用功能分區包括側邊欄控制聯邦輪數、客戶端數量、Non-IID濃度參數alpha、聚合算法選擇主區域一全局指標卡片準確率、F1、AUC、通信輪數主區域二訓練過程曲線每輪全局模型在測試集上的表現主區域三多算法對比柱狀圖和混淆矩陣熱力圖主區域四客戶端數據分布展示import streamlit as st import pandas as pd import matplotlib.pyplot as plt st.set_page_config(page_title聯邦學習成績預測系統, layoutwide) st.title(高校學生成績預測系統聯邦學習實驗平臺) with st.sidebar: st.header(實驗參數配置) num_clients st.slider(客戶端數量, 2, 10, 5, step1) alpha st.slider(Non-IID濃度參數α, 0.05, 1.0, 0.5, step0.05) comm_rounds st.slider(通信輪數, 5, 50, 20, step5) aggregation_algo st.selectbox(聚合算法, [FedAvg, FedYogi]) run_btn st.button(開始實驗, typeprimary)Streamlit有個小技巧按鈕點擊后執行長任務時界面會一直轉圈。我用了st.status或者加個進度條把聯邦訓練每一輪的指標實時寫回session_state界面輪詢刷新這樣用戶能實時看到訓練過程而不是干等一個結果。4.2 指標看板與圖表展示訓練完成后用st.metric展示核心指標對比的是FedAvg和FedYogi在同一組數據劃分下的表現col1, col2, col3, col4 st.columns(4) col1.metric(測試集準確率, f{metrics[accuracy]:.4f}) col2.metric(F1分數, f{metrics[f1]:.4f}) col3.metric(AUC, f{metrics[auc]:.4f}) col4.metric(通信輪數, f{comm_rounds})曲線部分我用matplotlib畫折線圖再通過st.pyplot渲染。相比st.line_chart底層是Altairmatplotlib可以自由控制坐標軸標簽、圖例和網格線更適合展示實驗類數據。圖表要表達的核心信息是隨著通信輪次增加全局模型在測試集上的F1如何變化FedYogi是否比FedAvg收斂更平滑。4.3 多算法對比模塊最后是重頭戲——把本地模型和聯邦模型的六個算法結果放在同一張柱狀圖上對比results_df pd.DataFrame({ 算法: [邏輯回歸, 決策樹, 隨機森林, XGBoost, FedAvg, FedYogi], F1分數: [0.621, 0.654, 0.703, 0.724, 0.718, 0.742], AUC: [0.712, 0.745, 0.783, 0.802, 0.795, 0.824] })從結果可以清楚看到隨機森林和XGBoost這類本地集成模型已經不錯了但聯邦學習模型憑借多客戶端數據融合的優勢在F1和AUC上都超過了單客戶端訓練的模型。加上混淆矩陣熱力圖可以直觀看到模型在掛科這個少數類上的查全率表現。5. 實驗數據與結果解讀5.1 實驗配置與數據集數據集我用了UCI的Student Performance原始特征包括學生家庭背景、學習時間、缺勤次數、歷史成績等30個字段。預處理時做了標簽編碼和數值標準化目標變量定義為數學成績是否低于10分葡萄牙評分體系10分及格。模擬了5個客戶端對應5個學院。不同alpha取值下客戶端數據分布差異明顯。超參數配置如下參數值客戶端數5本地訓練輪數3全局通信輪數20本地學習率0.01FedYogi學習率0.01批大小32模型結構3層全連接(30-64-2)5.2 實驗結果對比我跑了一組完整的對照實驗alpha設0.5結果整理如下方案準確率F1AUC本地邏輯回歸0.7120.6210.712本地決策樹0.7380.6540.745本地隨機森林0.7710.7030.783本地XGBoost0.7860.7240.802FedAvg0.7840.7180.795FedYogi0.7990.7420.824中心化理想訓練0.8150.7630.831幾個關鍵發現第一FedYogi在所有指標上都超過FedAvg且在訓練過程中收斂更平滑說明自適應優化器在Non-IID場景下確實有優勢。第二聯邦模型接近XGBoost甚至略超XGBoost但聯邦模型沒有接觸過任何其他客戶端的原始數據——在隱私保護的前提下達到接近中心化的效果這個結果很有說服力。第三中心化模型仍是理論上限說明聯邦學習目前還做不到完全無損但差距已經被壓縮到很小。5.3 Non-IID程度對收斂的影響換不同的alpha值跑同一套流程我觀察到明顯的規律alpha越小數據分布越偏斜FedAvg的收斂波動越大最終F1下降越多而FedYogi受alpha影響要小得多在alpha0.1這種極端Non-IID情況下FedYogi的F1比FedAvg高出約5個百分點。一個值得注意的現象是當某個客戶端上不掛科樣本占比接近95%本地模型幾乎失去預測能力——所有樣本都預測為不掛科也能拿95%準確率但F1直接崩盤。聯邦學習至少能通過全局模型的先驗知識兜底讓這個客戶端不至于完全喪失少數類的判別能力。6. 常見問題與排查技巧實錄6.1 聯邦訓練不收斂怎么辦最典型的癥狀是全局模型損失不降甚至越訓越差。排查順序一定是先看本地客戶端單訓能否收斂再看聚合邏輯是否有bug最后看參數設置。我的經驗是把服務端的全局模型參數直接打印出來跟上一輪對比如果聚合前后幾乎沒變化多半是聚合權重沒算對如果變化巨大多半是學習率太大或者模型初始化有問題。還有一個隱蔽的坑PyTorch模型在深拷貝時如果沒徹底調用copy.deepcopy而是直接賦值所有客戶端會共享同一個模型實例導致訓練時互相覆蓋參數。我一開始就踩了這個坑折騰了整整一個下午。6.2 客戶端數據分布的極端情況當某個客戶端只有極少數樣本或者只有一個類別的樣本時本地訓練的梯度會非常不穩定。我的處理方案是給每個客戶端設置最小樣本量閾值低于閾值的客戶端直接跳過本輪訓練沿用上一輪參數參與聚合。這比硬訓練一個垃圾模型要好得多。另外類別不平衡嚴重時客戶端本地損失函數建議切換成加權交叉熵給少數類更高的權重避免模型把所有樣本都推向多數類。6.3 Streamlit部署與性能問題Streamlit最讓人頭疼的是每輪交互都會重新執行整個腳本。我在代碼里用了st.cache_data裝飾數據加載函數讓數據集預處理只做一次聯邦訓練結果用st.session_state緩存避免切換側邊欄參數時重復訓練。另外深度學習模型用CPU訓練沒問題但在Streamlit里如果要實時訓練建議把訓練任務放到后臺界面只負責展示日志和進度否則前端會卡住。再有就是中文顯示問題matplotlib默認字體不包含中文字符集。我在繪圖前加了import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC] plt.rcParams[axes.unicode_minus] False不然圖例上全是方框特別掉檔次。6.4 災難性遺忘在聯邦場景中的表現實驗過程中我發現一個有意思的現象客戶端本地數據分布如果發生臨時漂移比如某學期考試難度突然變化本地模型對舊知識會出現災難性遺忘上傳參數后全局模型也會被帶偏。FedYogi對這個問題的抵抗力稍強一些因為它給每個參數維度分配了獨立學習率減少了大梯度更新對舊知識的沖刷。如果要進一步緩解可以在本地訓練時加一項正則約束當前模型不要偏離全局模型太遠這也是一種常見的聯邦學習改進方向。我在跑完這些實驗后的體會是聯邦學習真不是簡單地把集中訓練改成分布訓練就完事了數據分布、聚合策略、超參協同每個環節都會影響最終效果。如果你也在做類似的系統建議先把FedAvg跑通再加FedYogi最后再加可視化——一步步來每個階段的瓶頸都會更清晰。這套代碼后續還能往橫向聯邦、縱向聯邦、多任務學習方向擴展希望我的這些經驗能幫你少踩幾個坑。本文還有配套的精品資源點擊獲取