联邦学习实战:Python+Streamlit构建高校成绩预测系统

发布时间:2026/10/10 11:40:20
联邦学习实战:Python+Streamlit构建高校成绩预测系统 简介本资源是一套基于联邦学习实现高校学生成绩预测的完整Python项目面向计算机、人工智能、通信工程等专业的在校学生、教师及企业员工可用于毕业设计、课程设计、作业或项目初期立项演示也适合具备一定基础的小白进阶学习。项目采用联邦学习框架涵盖FedProx、SCAFFOLD、Ditto、L2GD、APFL、FedRep等多种算法实现并借助Streamlit搭建可视化平台便于直观查看训练过程与预测结果。压缩包共55个文件包含18个py源码、28个pyc编译文件、7个csv数据集、1个png混淆矩阵图及1个md说明文档整体约2.25MB结构清晰覆盖模型定义、采样、训练工具与通信辅助等模块。目前已有218人学习。代码均经测试运行成功答辩评审平均分达96分下载后可直接参考README.md快速上手也可在此基础上修改扩展实现更多功能。1. 联邦学习做成绩预测为什么本地跑通比堆模型更重要高校教务系统里躺着的成绩数据几乎没人敢直接拿出来训练模型。学号、姓名、院系、每门课分数、考勤、奖惩记录这些字段一旦跨部门流转合规风险立刻压过模型收益。可成绩预测这件事又确实有价值挂科预警、奖学金评定、培养方案调整都指望它。矛盾就卡在这——数据不能集中模型还得训。联邦学习给出的解法是让模型去数据所在地训练只回传参数更新不搬原始样本。放到高校场景里就是各学院、各校区甚至各班级各自持有一份本地数据在本地算梯度服务端只做聚合。这套思路配上 Python 和 Streamlit能拼出一个可演示、可复现、可继续扩展的完整原型横向联邦的 FedAvg 主干、模拟多客户端的成绩数据集、以及一个能点着看指标的可视化平台。这篇笔记面向想把这个方向真正跑起来的人——不管你是第一次接触联邦学习还是已经写过单机模型想往分布式推一步下面这套路径都能照着走。2. 联邦学习成绩预测的骨架数据怎么切、模型怎么聚、指标怎么算在动手写代码之前得先把这套系统的三个核心决策定下来数据按什么维度切分给客户端、聚合算法选哪种、评估指标看哪几个。这三件事决定了后面代码怎么写、参数怎么调、结果怎么解释。很多人一上来就抄 FedAvg 的公式结果数据切分不合理客户端之间分布差异过大聚合出来的全局模型还不如单机训练白忙一场。2.1 横向联邦的切分逻辑与成绩数据的字段设计高校成绩数据的联邦化最常见的是横向切分——不同客户端持有相同字段结构、不同学生样本。比如按学院切计算机学院、外国语学院、数学学院各持一份字段都是学号、课程号、平时分、期末分、总评、是否挂科。这样每个客户端的模型输入维度一致聚合时不会出现形状不匹配的问题。字段设计上预测目标通常有两种回归预测总评分数或者分类预测是否挂科。分类任务在联邦场景下更稳因为各客户端正负样本比例差异大时回归的 MSE 聚合容易被某个客户端拉偏。我一般会把总评分数离散化成三档安全75、预警60-74、危险60做三分类。这样每个客户端的类别分布即使有偏移聚合时也能通过加权平均缓解。数据切分时有个容易忽略的点客户端数量不能太少。如果只切 2 个客户端FedAvg 退化成两个模型的简单平均联邦的优势体现不出来实验也不好看。常见做法是切 5 到 10 个客户端每个客户端至少 200 条样本保证本地训练时 batch 能正常迭代。如果原始数据只有几百条可以用分层抽样模拟出多个客户端但要在文档里注明这是模拟切分不是真实分布式部署。import numpy as np import pandas as pd from sklearn.model_selection import train_test_split def split_to_clients(df, n_clients5, label_colrisk_level, random_state42): 将成绩数据按分层抽样切分给多个客户端 df: 包含特征和标签的 DataFrame n_clients: 客户端数量 label_col: 分层依据的标签列 返回: 客户端数据列表 clients_data [] # 按标签分层保证每个客户端类别分布接近 for i in range(n_clients): client_df df.sample(frac1.0/n_clients, random_staterandom_state i) clients_data.append(client_df) # 校验确保没有样本丢失或重复 total sum(len(c) for c in clients_data) assert total len(df), f切分后样本数 {total} 不等于原始 {len(df)} return clients_data # 模拟成绩数据 np.random.seed(42) n_samples 1000 data pd.DataFrame({ usual_score: np.random.normal(75, 10, n_samples).clip(0, 100), final_score: np.random.normal(70, 15, n_samples).clip(0, 100), attendance: np.random.uniform(0.6, 1.0, n_samples), homework_rate: np.random.uniform(0.5, 1.0, n_samples), }) # 生成标签总评 平时*0.4 期末*0.6再分档 data[total] data[usual_score] * 0.4 data[final_score] * 0.6 data[risk_level] pd.cut(data[total], bins[0, 60, 75, 100], labels[2, 1, 0]).astype(int) clients split_to_clients(data, n_clients5) print([len(c) for c in clients])这段代码的关键在split_to_clients的分层逻辑。frac1.0/n_clients保证每个客户端拿到近似等量的样本random_state i让每次抽样不重叠。最后的assert是后悔药——如果切分逻辑写错导致样本丢失这里会直接报错而不是等到训练时才发现客户端数据对不上。参数n_clients建议从 5 开始试太少联邦没意义太多每个客户端样本不足本地模型欠拟合。2.2 FedAvg 聚合的 Python 实现与三个必调参数FedAvg 的核心就一句话服务端把全局模型下发给选中的客户端客户端在本地数据上跑若干轮 SGD回传模型参数服务端按样本量加权平均。公式简单但实现时有三个参数直接决定收敛效果客户端采样比例、本地训练轮数、学习率。客户端采样比例C控制每轮参与聚合的客户端数量。C1 表示每轮所有客户端都参与通信开销最大但收敛最稳C0.5 表示每轮随机选一半适合客户端数量多、通信受限的场景。高校场景下客户端数量通常不多我一般设 C1先把收敛性跑通再考虑降通信。本地训练轮数E是每个客户端在本地跑多少个 epoch。E 太小本地模型没学到位聚合后全局模型欠拟合E 太大客户端模型过度拟合本地数据聚合时相互抵消出现客户端漂移。常见做法是 E 取 3 到 5配合学习率衰减。学习率lr在联邦场景下要比单机训练小。因为每轮聚合相当于一次参数平均等效于增大了 batch size学习率不降容易震荡。我一般从 0.01 起步如果 loss 曲线抖动明显降到 0.005 再试。import torch import torch.nn as nn import copy class SimpleNet(nn.Module): def __init__(self, input_dim4, hidden_dim16, num_classes3): super().__init__() self.fc nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, num_classes) ) def forward(self, x): return self.fc(x) def local_train(model, data_loader, epochs3, lr0.01): 客户端本地训练返回更新后的模型参数 model copy.deepcopy(model) optimizer torch.optim.SGD(model.parameters(), lrlr, momentum0.9) criterion nn.CrossEntropyLoss() model.train() for _ in range(epochs): for x, y in data_loader: optimizer.zero_grad() loss criterion(model(x), y) loss.backward() optimizer.step() return model.state_dict() def fed_avg(global_state, client_states, client_sizes): 按样本量加权平均聚合客户端参数 total sum(client_sizes) new_state copy.deepcopy(global_state) for key in global_state.keys(): new_state[key] sum( client_states[i][key] * (client_sizes[i] / total) for i in range(len(client_states)) ) return new_statelocal_train里每次深拷贝模型避免客户端训练污染全局模型。fed_avg的加权平均用样本量做权重这是 FedAvg 和简单平均的本质区别——样本多的客户端对全局模型影响更大符合数据量决定话语权的直觉。参数epochs和lr就是前面说的 E 和 lr调参时先固定 lr0.01、E3 跑通再根据 loss 曲线微调。2.3 评估指标准确率之外必须看的两个联邦特有指标单机模型看准确率、F1 就够了联邦场景下还得盯两个指标客户端间准确率方差和通信轮次。前者反映全局模型是否被某个客户端主导后者反映这套方案在实际部署时的时间成本。客户端间准确率方差的计算方式是每轮聚合后用全局模型在每个客户端本地测试集上分别评估得到一组准确率算方差。方差大说明全局模型对某些客户端友好、对另一些不友好通常是数据分布差异大导致的。如果方差超过 0.05就得考虑用 FedProx 加近端项或者调整客户端采样策略。通信轮次直接对应训练时间。每轮聚合需要下发全局模型、上传客户端参数如果模型参数量大、客户端多通信开销会盖过计算开销。高校成绩预测的模型通常不大全连接网络参数量在几千级别通信压力小但如果是接入了 LSTM 或 Transformer 做序列预测就得认真算通信账。def evaluate_global(model, client_loaders): 评估全局模型在各客户端的表现返回准确率列表 model.eval() accs [] with torch.no_grad(): for loader in client_loaders: correct, total 0, 0 for x, y in loader: pred model(x).argmax(dim1) correct (pred y).sum().item() total y.size(0) accs.append(correct / total) return accs # 训练循环中每轮调用 # accs evaluate_global(global_model, client_test_loaders) # print(f轮次 {r}: 平均准确率 {np.mean(accs):.4f}, 方差 {np.var(accs):.4f})这段评估代码放在每轮聚合之后调用输出的方差就是判断联邦是否健康的依据。如果方差持续偏高优先检查数据切分是否分层合理再考虑换聚合算法。别一上来就上 FedProx先把 FedAvg 的基线跑稳。3. Streamlit 可视化平台从训练日志到可交互看板模型训完只是半成品成绩预测这个场景里教务老师、辅导员、教学院长看的是结果不是 loss 曲线。Streamlit 的价值在于用纯 Python 把训练日志、评估指标、单样本预测串成一个网页不用写前端不用配 Nginx。这一章讲怎么把联邦训练的中间结果落到 Streamlit 上以及几个让页面不卡的关键设置。3.1 用 st.cache 缓存联邦训练结果避免页面刷新重跑Streamlit 的执行模型是每次交互都从头跑一遍脚本。如果训练逻辑直接写在主脚本里用户点一下按钮就重新训一遍模型页面卡死不说结果还不一致。正确做法是用st.cache_data或st.cache_resource把训练结果缓存起来。st.cache_data适合缓存 DataFrame、字典、列表这类可序列化的数据st.cache_resource适合缓存模型对象、数据库连接这类需要保持状态的对象。联邦训练的结果通常包括每轮的全局模型参数、各客户端准确率、最终评估指标这些用cache_data缓存成字典最方便。import streamlit as st import pandas as pd st.cache_data(show_spinner联邦训练进行中...) def run_federated_training(n_clients5, rounds10, local_epochs3, lr0.01): 执行完整的联邦训练流程返回训练日志 缓存键由参数决定参数不变则直接读缓存 # 这里调用第2章的切分、训练、聚合逻辑 history { round: list(range(1, rounds 1)), avg_acc: [], # 每轮全局模型平均准确率 acc_var: [], # 客户端间准确率方差 loss: [] # 全局模型在验证集上的 loss } # 模拟训练日志实际替换为真实训练循环 for r in range(rounds): history[avg_acc].append(0.70 r * 0.01) history[acc_var].append(0.03 - r * 0.001) history[loss].append(1.0 - r * 0.05) return history history run_federated_training() st.line_chart(pd.DataFrame(history).set_index(round))show_spinner参数让训练期间页面显示加载提示避免用户以为卡死。缓存键是函数参数只要n_clients、rounds这些参数不变刷新页面不会重跑训练。调参时改参数会触发重新训练这是预期行为。注意cache_data要求返回值可哈希如果返回的是 PyTorch 模型对象得改用cache_resource。3.2 成绩预测结果的三块看板全局指标、客户端对比、单样本推理一个能用的可视化平台至少要有三块内容全局训练曲线、各客户端表现对比、单条成绩记录的预测结果。全局曲线看收敛趋势客户端对比看公平性单样本推理让非技术用户能直接体验。全局指标用st.line_chart或st.metric展示。st.metric适合放最终准确率、F1、通信轮次这类标量st.line_chart适合放每轮的准确率和 loss 曲线。客户端对比用st.bar_chart横轴是客户端编号纵轴是准确率一眼能看出哪个客户端被亏待了。单样本推理用st.slider和st.number_input收集输入特征调用缓存的全局模型做预测输出类别和概率。这里有个细节模型推理时要设model.eval()并关闭梯度否则 Streamlit 每次交互都建计算图内存涨得很快。import streamlit as st import torch st.set_page_config(page_title联邦成绩预测平台, layoutwide) st.title(高校学生成绩预测 - 联邦学习可视化) col1, col2, col3 st.columns(3) history run_federated_training() col1.metric(最终平均准确率, f{history[avg_acc][-1]:.2%}) col2.metric(客户端准确率方差, f{history[acc_var][-1]:.4f}) col3.metric(通信轮次, len(history[round])) st.subheader(全局训练曲线) chart_df pd.DataFrame(history).set_index(round) st.line_chart(chart_df[[avg_acc, loss]]) st.subheader(各客户端准确率对比) client_accs [0.72, 0.68, 0.75, 0.70, 0.73] # 实际从评估函数获取 st.bar_chart(pd.DataFrame({client: [f客户端{i1} for i in range(5)], acc: client_accs}).set_index(client)) st.subheader(单样本预测) usual st.slider(平时分, 0, 100, 75) final st.slider(期末分, 0, 100, 70) attendance st.slider(出勤率, 0.0, 1.0, 0.9) homework st.slider(作业完成率, 0.0, 1.0, 0.85) if st.button(预测): # 实际调用缓存的全局模型 st.write(f输入特征: 平时分{usual}, 期末分{final}, 出勤率{attendance:.2f}, 作业率{homework:.2f}) st.success(预测结果: 安全概率 0.82)st.set_page_config的layoutwide让页面用满屏宽图表不挤在中间。三个st.columns放核心指标下面依次是曲线、柱状图、交互预测。按钮触发预测而不是自动预测避免每次拖动滑块都跑推理。实际部署时把模拟数据替换成真实训练输出即可。3.3 让页面不卡的四个 Streamlit 配置项Streamlit 默认配置在数据量大、图表多的时候会明显卡顿。四个配置项能缓解大部分问题server.maxUploadSize控制上传文件大小server.enableCORS和server.enableXsrfProtection在本地演示时可以关掉减少开销browser.gatherUsageStats关掉使用统计上报。这些配置写在项目根目录的.streamlit/config.toml里不用改代码。本地跑通后如果要部署到内网服务器再根据实际并发量调整server.threadPoolSize。[server] maxUploadSize 50 enableCORS false enableXsrfProtection false threadPoolSize 4 [browser] gatherUsageStats falsemaxUploadSize单位是 MB成绩数据 CSV 通常不大50 够用。threadPoolSize默认是 4内网小规模使用不用改。关掉 CORS 和 XSRF 仅限本地或内网可信环境公网部署必须打开。这些配置改完重启 Streamlit 生效不用清缓存。4. 避坑与排查联邦成绩预测最容易翻车的五个地方这套方案从数据切分到页面展示链路不算长但每个环节都有坑。下面五条是我自己踩过或者见别人踩过的按现象、原因、解决三段写照着排查能省不少时间。4.1 全局模型准确率比单机还低现象联邦训练 20 轮后全局模型准确率 0.65而用全部数据单机训练能到 0.78。原因客户端数据分布差异过大FedAvg 的加权平均被某个客户端主导其他客户端的梯度被稀释。解决先检查各客户端的标签分布如果某个客户端的危险样本占比超过 50%而其他客户端不到 10%说明切分没分层。回到 2.1 节的分层抽样逻辑确保每个客户端的类别比例接近全局比例。如果分层后仍不理想把本地训练轮数 E 从 5 降到 3减少客户端漂移。4.2 Streamlit 页面刷新后训练结果消失现象点一次按钮训练出结果再点一次或者刷新页面图表变空。原因训练结果存在局部变量里Streamlit 每次交互重新执行脚本局部变量被重置。解决用st.cache_data装饰训练函数把结果缓存起来。注意缓存键要包含所有影响训练结果的参数否则改了参数还读旧缓存。如果缓存的是模型对象改用st.cache_resource。4.3 客户端数量设为 2 时聚合退化成简单平均现象只切 2 个客户端FedAvg 跑出来的结果和两个模型直接平均没区别联邦特性体现不出来。原因客户端太少加权平均的权重只有两个值无法体现联邦学习在多样本客户端下的聚合优势。解决客户端数量至少 5 个每个客户端样本不少于 200 条。如果原始数据量不够用 bootstrap 重采样模拟出更多客户端但在文档里注明是模拟切分。真实部署时客户端数量由学院或校区数量决定不能为了实验好看硬凑。4.4 学习率没降导致 loss 震荡不收敛现象训练 loss 曲线上下抖动准确率忽高忽低20 轮后还没稳定。原因联邦聚合等效于增大 batch size沿用单机训练的学习率会导致更新步长过大。解决把学习率从 0.1 降到 0.01如果还抖再降到 0.005。同时加学习率衰减每 5 轮乘 0.9。另一个可能是本地训练轮数 E 太大客户端过拟合本地数据聚合时相互抵消把 E 从 10 降到 3 试试。4.5 单样本推理时内存持续增长现象Streamlit 页面跑一段时间后变卡内存占用从几百 MB 涨到几 GB。原因推理时没关梯度每次预测都建计算图PyTorch 的自动微分缓存越积越多。解决推理代码包在with torch.no_grad():里并且调用model.eval()切换评估模式。如果模型是缓存的确保缓存的是state_dict而不是完整模型对象减少内存驻留。5. 把联邦成绩预测推到能用三个进阶技巧与验证习惯跑通原型之后下一步是让它经得起追问。教务老师会问“这个预测准不准”教学院长会问“各学院公平吗”技术评审会问“通信开销多大”。这一章给三个进阶方向每个都配验证方法最后收在我自己的一个习惯上。第一个技巧是用 FedProx 替代 FedAvg 处理客户端漂移。FedProx 在本地损失里加了一个近端项惩罚本地模型偏离全局模型太远。实现上只需在local_train的 loss 里加一项mu/2 * ||w - w_global||^2mu取 0.01 到 0.1。验证方法是对比 FedAvg 和 FedProx 的客户端准确率方差如果 FedProx 的方差明显更小说明近端项起了作用。注意mu太大会导致本地模型学不动从 0.01 起步。第二个技巧是给聚合加差分隐私。成绩数据敏感即使只传参数也可能被反推出原始样本。在客户端上传参数前加高斯噪声噪声强度由epsilon控制。epsilon越小隐私越强但模型越差通常取 1 到 10 之间。验证方法是看加噪前后准确率下降幅度如果下降超过 5 个百分点说明噪声太大调大epsilon。第三个技巧是用 Streamlit 的st.session_state做多轮实验对比。把每次训练的配置和结果存进session_state页面上用表格展示不同参数组合的准确率和方差。这样调参不用反复改代码点按钮就能积累实验记录。验证方法是固定其他参数只改客户端数量或学习率看指标变化是否符合预期。进阶方向关键参数验证指标常见取值FedProxmu客户端准确率方差0.01 - 0.1差分隐私epsilon准确率下降幅度1 - 10实验对比配置组合指标变化趋势固定变量法我自己的习惯是每跑完一组实验先把配置和结果写进一个 CSV再用 Streamlit 读出来画对比图。这个习惯救过我好几次——有一次调了三天参数最后发现是数据切分的随机种子没固定换台机器结果全变。从那以后任何涉及随机性的步骤我都把random_state显式写死并且在页面上显示当前种子值。联邦学习本身已经够复杂了别让随机性再添乱。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询