简介:该资源面向计算机、人工智能、数据科学等专业的学生与开发者,提供一套基于联邦学习的高校学生成绩预测完整研究方案,可用于毕业设计、课程设计或初期项目立项演示。项目采用Python实现,涵盖FedProx、Ditto、APFL、FedRep等多种联邦学习算法,并借助Streamlit搭建了简易可视化平台,便于直观查看训练过程与预测结果。压缩包共55个文件,以18个py源码、28个pyc编译文件、7个csv数据集及1个png混淆矩阵图、1个md说明文档为主,整体约2.25MB,结构清晰,便于按模块阅读与二次开发。资源内代码均经测试运行成功,功能正常。目前已有272人学习下载,读者可从中获取完整的联邦学习建模流程、多算法对比实验代码、样本数据与评估结果,适合作为实战练习、算法复现与论文写作的参考素材。
1. 联邦学习做成绩预测:为什么“数据不出校”比模型精度更难搞定
高校学生成绩预测这件事,单校数据量小、特征维度高、分布差异大,直接把几所学校的数据汇总到一台服务器上训练,精度确实能上去,但学籍、成绩、行为记录这些数据的跨校流转,在合规层面基本走不通。联邦学习给出的思路是:模型参数可以走,原始数据不动,各校在本地训练、只上传梯度或权重,由聚合端合并成全局模型。这个标题对应的方案,就是把这套机制落到成绩预测任务上,再用 Streamlit 搭一个能看、能点、能演示的可视化平台。它适合两类人:一类是想把联邦学习从论文公式拉到可运行代码的在校开发者,另一类是手里有多校脱敏数据、想验证“不集中数据能不能训出可用模型”的工程同学。下面按我实际搭这套东西的顺序讲,重点放在能复现的步骤和参数上。
2. 成绩预测任务怎么拆成联邦学习能吃的形状
2.1 先定标签和特征,别急着上联邦框架
成绩预测的标签通常有两种口径:一种是回归,直接预测某门课的期末百分制分数;另一种是分类,把学生划成“及格/不及格”或“优良中差”几档。我一般优先做二分类,因为联邦场景下各校样本量差异大,回归的 MSE 对分布偏移太敏感,分类的 AUC 和 F1 更容易横向比较。特征侧常见的是三类:历史成绩类(前序课程均分、学分绩点)、行为类(出勤、作业提交率、平台登录频次)、静态类(专业、年级、入学方式)。这里有个血泪经验:行为类特征在不同学校的采集口径差别极大,A 校有登录频次,B 校可能只有考勤,直接对齐会引入大量缺失。稳妥做法是先做特征交集,只保留各参与方都有的字段,缺失率超过 40% 的列直接砍掉。
标签泄漏是另一个高频翻车点。比如用“本学期总评成绩”去预测“本学期期末成绩”,两者高度相关,模型 AUC 能到 0.99,但上线毫无意义。构造特征时必须严格按时间切分:用第 1 到第 N-1 周的记录预测第 N 周之后的结果,任何在预测时点之后才产生的字段都不能进特征表。
2.2 联邦学习的三种聚合路线,成绩预测该选哪种
联邦学习落地时主要有三条路线。横向联邦(Horizontal FL)适合各校特征相同、样本不同的情况,这也是成绩预测最常用的设定:每所学校是一个 client,本地数据不出校,只上传模型更新。纵向联邦(Vertical FL)适合同一批学生、不同机构持有不同维度数据,比如学校有成绩、某平台有行为记录,但需要样本对齐,工程复杂度高。联邦迁移(FedTransfer)适合各校特征空间差异大、只有少量重叠的场景,成绩预测里用得少。
对成绩预测,我建议从横向联邦的 FedAvg 起步。原因是各校特征经过交集对齐后基本一致,样本按学生自然分布在不同 client,聚合逻辑清晰。选型时还要确认一件事:各校标签分布是否严重倾斜。如果 A 校不及格率 5%、B 校 30%,FedAvg 的简单加权平均会被大样本 client 主导,需要引入按类别重加权或 FedProx 这类带近端项的算法。
2.3 用 PyTorch 写一个最小可跑的本地训练器
联邦学习的 client 端本质就是一个普通训练脚本,区别在于数据不落盘到中心、训练完只导出参数。下面是一个二分类成绩预测的本地训练器骨架,输入是各校自己的 CSV,输出是 state_dict。
import torch import torch.nn as nn import pandas as pd from torch.utils.data import DataLoader, TensorDataset # 成绩预测用的轻量 MLP,输入维度按特征数调整 class GradeNet(nn.Module): def __init__(self, in_dim): super().__init__() self.net = nn.Sequential( nn.Linear(in_dim, 64), nn.ReLU(), nn.Dropout(0.3), # 防过拟合,小样本校尤其需要 nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 2) # 二分类:及格/不及格 ) def forward(self, x): return self.net(x) def load_local_data(csv_path, feature_cols, label_col): df = pd.read_csv(csv_path) # 缺失值用列均值填充,联邦场景下不能跨校统计,只能本地填 df[feature_cols] = df[feature_cols].fillna(df[feature_cols].mean()) X = torch.tensor(df[feature_cols].values, dtype=torch.float32) y = torch.tensor(df[label_col].values, dtype=torch.long) return TensorDataset(X, y) def local_train(model, loader, epochs=5, lr=1e-3): opt = torch.optim.Adam(model.parameters(), lr=lr) loss_fn = nn.CrossEntropyLoss() model.train() for _ in range(epochs): for xb, yb in loader: opt.zero_grad() loss = loss_fn(model(xb), yb) loss.backward() opt.step() return model.state_dict()这段代码里三个参数最关键。Dropout(0.3)不是随便写的,小样本学校(几百条记录)很容易过拟合,dropout 低于 0.2 时验证集 AUC 会明显掉。epochs=5是本地轮数,联邦里叫 local epoch,设太大 client 会跑偏、聚合后震荡,设太小全局收敛慢,5 到 10 是常见起点。lr=1e-3配合 Adam,如果某校样本量特别小,可以降到 5e-4 减少单轮漂移。注意缺失值填充必须在本地做,不能把各校均值汇总后再回填,那等于泄露了分布信息。
3. 把 FedAvg 聚合端和 Streamlit 平台接起来
3.1 聚合端的加权平均逻辑与通信轮次控制
聚合端要做的事很明确:收集各 client 的 state_dict,按样本量加权平均,再把新全局模型发回去。加权是关键,样本多的学校话语权大,但也不能让小学直接被淹没,所以常见做法是样本量开方后再归一化,缓解极端不平衡。
import torch import copy def fed_avg(global_model, client_states, client_sizes): # 按样本量开方加权,避免大 client 完全主导 weights = [s ** 0.5 for s in client_sizes] total = sum(weights) weights = [w / total for w in weights] new_state = copy.deepcopy(global_model.state_dict()) for key in new_state.keys(): new_state[key] = sum( w * cs[key].float() for w, cs in zip(weights, client_states) ) global_model.load_state_dict(new_state) return global_model通信轮次(round)一般设 20 到 50。轮次太少全局模型没学到跨校共性,太多则通信开销大且可能过拟合到某些 client。我通常每轮记录全局模型在各方验证集上的 AUC,画一条曲线,当连续 5 轮 AUC 波动小于 0.005 就提前停。client_sizes必须用各校真实训练样本数,不能用测试集数量,否则加权失真。
3.2 Streamlit 页面要展示哪四块内容
可视化平台不是把训练日志堆上去就行,得让看的人三秒内明白联邦到底带来了什么。我一般放四块:第一块是数据概览,各校样本量、正负样本比例、特征缺失率,用表格加柱状图;第二块是训练过程,每轮全局 AUC、Loss 曲线,以及各 client 的本地指标对比;第三块是模型对比,集中式训练(把数据合到一起)和联邦训练的指标并排,直观看出精度损失有多大;第四块是单样本预测,输入一个学生的特征,输出及格概率,让非技术同学也能点着玩。
import streamlit as st import pandas as pd import plotly.express as px st.set_page_config(layout="wide") st.title("联邦学习成绩预测平台") # 第一块:各校数据概览 st.header("参与方数据概览") summary = pd.read_csv("client_summary.csv") # 列:学校代号,样本数,不及格率 st.dataframe(summary) fig = px.bar(summary, x="学校代号", y="样本数", color="不及格率") st.plotly_chart(fig, use_container_width=True) # 第二块:训练曲线 st.header("联邦训练过程") history = pd.read_csv("round_metrics.csv") # 列:round,global_auc,global_loss st.line_chart(history.set_index("round")[["global_auc", "global_loss"]])client_summary.csv和round_metrics.csv由训练脚本每轮追加写入,Streamlit 只负责读和画,不要把训练逻辑塞进页面回调里,否则刷新一次重训一次,演示时必翻车。use_container_width=True让图表自适应,投影演示时不会出现横向滚动条。
3.3 本地跑通的最小命令与目录约定
整套东西的目录我习惯这样放:data/下按client_1.csv、client_2.csv命名,models/存每轮全局模型,logs/存指标 CSV,server.py是聚合入口,client.py是本地训练入口,app.py是 Streamlit 页面。跑通顺序是先分别跑各 client 生成初始参数,再跑 server 聚合,最后起页面。
# 1. 各校本地训练一轮,导出参数 python client.py --data data/client_1.csv --out models/c1_round0.pt python client.py --data data/client_2.csv --out models/c2_round0.pt # 2. 聚合端加载各校参数,执行 FedAvg python server.py --clients models/c1_round0.pt models/c2_round0.pt \ --sizes 800 1200 --rounds 30 --out models/global.pt # 3. 启动可视化平台 streamlit run app.py --server.port 8501--sizes传的是各校训练样本数,顺序必须和--clients一致,这是最容易错的地方,顺序反了加权就反了,全局模型会偏向错误的一方。--rounds 30是上限,实际会按早停条件提前结束。端口用 8501 是 Streamlit 默认,演示机如果被占用就换 8502,别去改代码里的硬编码。
4. 联邦成绩预测最容易翻车的五个地方
4.1 各校特征列名不一致,对齐后全是 NaN
现象:聚合后全局模型 AUC 只有 0.5 左右,查各校本地验证却正常。原因:A 校列名是attendance_rate,B 校是attend_rate,合并时按列名对齐,结果两边互相填 NaN,填充后又都变成均值,特征失效。解决:在数据准备阶段强制一份feature_schema.json,所有 client 按同一份 schema 重命名列,缺列直接报错退出,不要静默填充。
4.2 标签定义不统一,及格线各校不同
现象:联邦模型在 A 校表现好、B 校差。原因:A 校 60 分及格,B 校某课程 50 分就算过,标签口径不一致,全局模型学到一个折中的决策边界,两边都不讨好。解决:联邦启动前先对齐标签生成规则,统一用百分制 60 分或统一用绩点阈值,规则写进项目说明,各校按同一份脚本重新打标。
4.3 本地 epoch 设太大,全局模型震荡不收敛
现象:全局 AUC 曲线上下跳,几十轮都不稳。原因:local epoch 设成 20 甚至 50,每个 client 在本地过拟合自己的数据分布,聚合时方向互相抵消。解决:把 local epoch 降到 5 左右,配合学习率 1e-3,如果还不稳就上 FedProx,在本地损失里加近端项约束 client 不要跑太远。
4.4 用测试集样本量做聚合权重
现象:某校明明数据少,但全局模型明显偏向它。原因:聚合时client_sizes误传了测试集数量,而测试集是人为平衡过的,导致权重失真。解决:权重必须用训练集真实样本数,训练脚本里把len(train_loader.dataset)直接传给聚合端,不要手工填。
4.5 Streamlit 页面每次刷新都重训模型
现象:演示时页面卡死,CPU 跑满。原因:把训练逻辑写在了页面顶层或按钮回调里,Streamlit 每次交互都重跑整个脚本。解决:训练和展示彻底分离,训练产物落盘成.pt和.csv,页面只读文件;如果确实要页面触发训练,用st.cache_resource包住模型加载,并且只加载不重训。
5. 让联邦成绩预测从能跑变成能信:验证与调参的收尾技巧
模型能跑通只是第一步,真正决定这套方案值不值得投入的,是你能不能证明“联邦的精度损失在可接受范围内”。我的习惯是固定一个基线:把所有 client 数据合到一起做集中式训练,得到一个上界 AUC,然后看联邦模型能到它的百分之多少。经验上,横向联邦在成绩预测这类表格任务上,AUC 损失通常在 0.02 到 0.05 之间,如果损失超过 0.1,基本可以判定是特征对齐或标签口径出了问题,而不是算法本身不行。
验证时别只看全局 AUC,要按 client 拆开看。全局指标好看但某个小学掉得厉害,说明加权策略或本地 epoch 需要调。我一般会做一张对比表,把集中式、FedAvg、FedProx 三种设定在各校验证集上的 AUC 和 F1 并排列出,谁在什么条件下更稳一目了然。
| 设定 | 全局 AUC | 小学 A AUC | 大学 B AUC | 通信轮次 |
|---|---|---|---|---|
| 集中式基线 | 0.86 | 0.84 | 0.87 | 不适用 |
| FedAvg | 0.82 | 0.76 | 0.84 | 30 |
| FedProx | 0.83 | 0.79 | 0.84 | 30 |
调参顺序我建议这样:先固定 local epoch=5、lr=1e-3 跑一轮 FedAvg,看各校差距;差距大就把聚合权重从样本量改成开方加权;还不行再上 FedProx,把近端项系数从 0.01 开始试,太大模型会退化成各校独立训练。通信轮次不用盲目加,早停条件设好,省下来的时间拿去做特征工程,收益比多跑二十轮大得多。
最后一个具体技巧:把每轮的全局模型和各校本地模型都存下来,页面里加一个“回放”下拉框,能切换查看不同轮次的效果。演示时这比任何文字说明都有说服力,评审的人自己点几下就明白联邦到底在干什么。我自己踩过最深的坑是早期没存中间轮次,结果要对比时只能重训,白白多花一晚上。希望帮到你。
本文还有配套的精品资源,点击获取