☰
基于联邦学习的高校学生成绩预测系统:数据不出校门也能训练模型
2026/10/27 12:15:02 网站建设 项目流程

简介:基于联邦学习的高校学生学业成绩预测系统,包含完整的Python工程与配套数据集,通过Streamlit构建可视化操作界面,适合计算机、人工智能、通信、自动化等专业学生用于毕业设计、课程设计与科研入门。项目为个人毕设,答辩评审分达95分,代码已调试通过,可运行复现。压缩包内共67个文件,大小约6.02MB,主要包含Python源代码(py)、编译缓存(pyc)、学生成绩与训练过程数据(csv)、关键文件备份(zbak)、知识扩展压缩包、图表结果(png)以及说明文档(md),不同模块对应联邦学习中的多种算法策略,如FedRep、FedProx、Ditto、APFL等,并记录精度与损失曲线供分析。目前已有112人学习浏览,适合具有一定Python基础、希望深入联邦学习应用的读者,可在现有框架上修改数据与模型结构,快速搭建自己的成绩预测或横向联邦实验。资源来源于网络分享,仅供学习交流,请勿商用。

1. 高校学生成绩预测为什么值得用联邦学习:数据不出校门也能把模型训起来

很多做教学管理系统的团队,第一次看到“高校学生学业成绩预测系统基于联邦学习”这个组合时,第一反应是“成绩预测我熟,但联邦学习是不是在给自己加戏”。如果只是单个部门、单所学校做预测,集中式训练确实更直接;但真实场景里,成绩数据分散在教务处、学工部、各院系甚至多所合作高校手里,直接汇总学生挂科记录、考勤和作业数据,既涉及隐私合规,又会在部门协调上卡壳。联邦学习解决的就是这个矛盾:模型参数流动,原始数据不流动。这套方案用 Python 实现,配合 Streamlit 做可视化,落地门槛其实比想象中低。本文会从数据构造、联邦训练实现到界面搭建,给你一条能照着跑的完整链路。

2. 先看清这条技术链路:从学生画像到联邦训练的数据流与选型理由

2.1 成绩预测任务在预测什么:回归与分类的界定

做预测系统,第一步不是选模型,而是把任务定义清楚。成绩预测通常有两种口径:一是预测期末考试的百分制分数,这是回归问题,评估指标用 RMSE、MAE;二是预测学生是否挂科或者成绩等级(优/良/中/及格/不及格),这是分类问题,评估指标用准确率、F1。联邦学习的训练框架对两者都支持,差别只在输出层和损失函数。回归任务用线性输出加 MSE 损失,分类任务用 Softmax 输出加交叉熵损失。

我一般建议第一次落地的人先从二分类做起——预测“是否会挂科”。原因很实际:挂科预警是教务系统最能直接产生价值的场景,而且二分类的评估结果更容易向非技术人员解释清楚。当你把二分类跑通、完整验证了联邦学习流程之后,再扩展到回归任务或五分类,只是改动模型输出层和评估函数的事。

特征设计上,两类任务共用同一套基础特征。成绩预测最有效的四类特征:历史考试成绩(上学期绩点、期中成绩)、学习行为特征(出勤率、作业提交率、图书馆门禁记录)、课程属性特征(课程类别、学分、开课学期)、学生基本信息(年级、专业类别)。注意,学生姓名、学号、身份证号这类标识信息绝不能进模型,联邦学习保护隐私的前提是你本身就不该收集多余的个人标识。

2.2 为什么不是集中式训练:隐私与数据孤岛的真实约束

如果你在单所学校内部做试点,管理上允许你把教务数据和考勤数据拉到一个表里,那集中式训练在技术上是更省事的。但跨学院、跨学校协作时,联邦学习的价值就体现出来了。举个例子,A 校想预测大一新生的高数挂科概率,但仅靠 A 校自己的历史数据,建模样本量可能只有几千条,模型容易过拟合;如果能把 B 校、C 校的同类数据也利用起来,预测效果通常会有明显提升。可 B 校、C 校的数据不能出校门,因为涉及学生个人隐私和校际数据共享合规问题。

联邦学习干的活就是“数据不动模型动”:每个参与方用自己的本地数据训练模型,只把模型参数(权重和偏置)上传到服务端,服务端聚合后把全局模型下发给各参与方,重复迭代。整个过程中没有任何原始数据离开参与方本地。这里要区分清楚,联邦学习保护的是训练数据不直接泄露,但模型参数本身携带信息,恶意方仍可能通过梯度反推训练数据特征,所以实际部署中还会叠加差分隐私、机密计算等加固手段。作为入门实现,先把 FedAvg 跑通,理解参数聚合机制,是更务实的起点。

2.3 联邦学习参与方架构:学校、院系、教务的三种角色划分

从系统架构看,这套成绩预测系统是典型的横向联邦学习结构,也就是各参与方的数据特征空间相同、样本不同。三种角色这样划分:服务端角色通常由校级信息化部门或项目牵头方承担,负责初始化全局模型、分发模型参数、聚合各客户端上传的参数;客户端角色是各院系或合作院校,每个客户端持有自己教学管理系统中本地学生的成绩和行为数据;测试方角色是教务管理人员,他们通过界面查看训练进度、模型评估结果和最终预测名单。

这种划分在工程实现上有个直接好处:每一方只需要暴露一个接口——客户端往服务端传参数,服务端往客户端发参数。数据表结构不需要统一迁移,只要字段映射关系约定好。后面第 4 章的代码就是按照这套角色分工来组织的。实际开发中,很多人会纠结“我只有一个数据集,怎么做联邦训练”——解决办法是模拟多客户端,把数据切分给多个本地训练进程,第 3 章会专门讲怎么切。

3. 数据集准备与联邦切分:把一份学生数据改造成多方本地数据

3.1 成绩预测数据集的核心字段与构造方法

如果你手头没有真实教务数据,最常见的做法是使用公开的学生成绩数据集。学术圈和 Kaggle 上流传较广的是 UCI 机器学习库中的 Student Performance 数据集,包含两所葡萄牙中学学生的成绩、出勤、家庭背景等字段。它的数据量不大(约 1000 条),但字段结构很适合教学演示。另一个方向是自己构造模拟数据——用 Python 的 Faker 库或 NumPy 随机生成学生特征,再根据真实成绩分布的统计规律(比如平时成绩高的学生期末挂科概率低)注入相关性。

字段至少包含这些:student_id(用于数据关联,训练前必须删除或替换为匿名 ID)、course_id(课程编号)、attendance_rate(出勤率,0-1 小数)、homework_avg(作业平均分,0-100)、midterm_score(期中成绩,0-100)、credit(学分)、semester(学期序号)、final_score(期末成绩,作为标签)。如果你做二分类,把 final_score 按阈值映射成 0/1,例如低于 60 分记为 1(挂科)。

数据清洗有个容易忽略的边界:缺勤记录和缓考记录。很多教務系统里,缺考学生的 final_score 字段是空缺的,或者用 -1 标记。如果直接把这些记录当 0 分跑进模型,会严重拉偏预测结果。稳妥做法是单独建一张表保留缺考学生名单,模型训练时排除这些样本,预测阶段再单独规则预警。类似这类细节,做试点的时候一定让教务处的人参与字段口径确认,否则模型上线一周你就会因为误报率太高被投诉。

3.2 数据标准化与非独立同分布切分策略

拿到原始数据后,第一件事是标准化。成绩类特征(平时成绩、期中成绩)量纲是 0-100,行为类特征(出勤率)量纲是 0-1,如果不做标准化,模型训练时高量纲特征会主导梯度更新。这里推荐用 sklearn 的 StandardScaler,按每个特征列做 z-score 归一化。注意一个隐蔽的坑:标准化参数必须只在各客户端本地数据上拟合,不能把所有客户端数据合并后统一拟合。否则每个客户端上传的梯度里隐含的分布信息,会在服务端被无意汇总,这在联邦学习的隐私语义下是不规范的操作。

from sklearn.preprocessing import StandardScaler def preprocess_local_data(df, feature_cols, fit_scaler=True, scaler=None): if fit_scaler: scaler = StandardScaler() scaler.fit(df[feature_cols]) df[feature_cols] = scaler.transform(df[feature_cols]) X = df[feature_cols].values.astype("float32") y = df["label"].values.astype("int64") return X, y, scaler

这段逻辑说明:每个客户端首次处理数据时 fit_scaler 传 True,生成自己的 scaler 并保存;后续训练轮次直接传入保存的 scaler。参数说明:feature_cols 是需要标准化的特征列列表;label 列是二分类标签;返回的 X、y 会被封装成 PyTorch Dataset 使用。

联邦学习里有一个比标准化更关键的步骤——构造 Non-IID(非独立同分布)数据分布。真实场景中各院系的学生水平天然不同:计算机学院的高数平均 80 分,某文科专业可能平均 68 分。如果模拟实验把所有数据均匀切分给各客户端,训练出的模型会过于乐观,真实部署时直接翻车。构造 Non-IID 的常见做法是按标签分布或分数段切分:把数据先按成绩排序,按客户端数量分成若干分位区间,让每个客户端拿到一个分数段的样本。

import numpy as np def non_iid_split(X, y, num_clients): sort_idx = np.argsort(y) X_sorted, y_sorted = X[sort_idx], y[sort_idx] chunks = np.array_split(y_sorted, num_clients) client_data = [] start = 0 for chunk in chunks: end = start + len(chunk) client_data.append((X_sorted[start:end], y_sorted[start:end])) start = end return client_data

这段代码的思路是:按标签值排序后分成 num_clients 份,每个客户端拿到一个连续区段。第 0 块人全是低分样本,最后一块全是高分样本。如果你的二分类数据是 0/1 标签,这样切分会导致某些客户端全是没有负样本的正常学生,恰好模拟现实中“某学院几乎没人挂科”的情况。

一个参数取舍:num_clients 不宜过大,我一般设 4 到 8 个。客户端太多会导致每个客户端数据量太少,本地模型的梯度估计噪声大,聚合后模型迟迟不收敛。

3.3 本地数据集封装:用 PyTorch 的 Dataset 类管理各参与方数据

每轮联邦训练中,客户端要反复从本地数据集中取批次训练。直接用 NumPy 数组虽然可行,但混用数据结构会让代码很快失控。建议每个客户端封装一个 PyTorch Dataset,职责统一。

import torch from torch.utils.data import Dataset, DataLoader class StudentDataset(Dataset): def __init__(self, X, y): self.X = torch.tensor(X, dtype=torch.float32) self.y = torch.tensor(y, dtype=torch.float32).unsqueeze(1) def __len__(self): return len(self.y) def __getitem__(self, idx): return self.X[idx], self.y[idx] def create_client_dataloaders(client_data, batch_size=32): loaders = [] for X, y in client_data: dataset = StudentDataset(X, y) loader = DataLoader(dataset, batch_size=batch_size, shuffle=True) loaders.append(loader) return loaders

逻辑说明:StudentDataset 把特征转为 float32 张量,标签转为 float32 并添加一个维度,这是为了匹配二分类模型输出的形状(每个样本输出一个概率值)。create_client_dataloaders 遍历第 3.2 节切分好的客户端数据,为每个客户端生成独立的 DataLoader。参数说明:batch_size 设 32 是折中选择,样本总量只有几千条时 batch_size 太大容易让模型陷入局部最优。每个 DataLoader 中 shuffle=True 必不可少,因为 Non-IID 切分后数据本身有顺序,不洗牌模型会在局部梯度上反复震荡。

如果样本量有明显倾斜——比如某个客户端只有 50 条记录而另一个有 2000 条——服务端聚合时就要按样本量加权,而不是简单平均。这在第 4 章会说到。这里先留个印象:客户端本地数据量差异过大的问题是模拟实验中最容易忽略、真实部署时最先暴露的坑。

4. 核心实现:用 Python 从零写一个 FedAvg 成绩预测训练器

4.1 模型结构选择:MLP 输入特征怎么设计

联邦学习框架不限制模型类型,但成绩预测这个任务用多层感知机(MLP)就够了。卷积神经网络、Transformer 在这里收益很低——成绩表是结构化表格数据,没有图像或文本的空间结构。MLP 的三个隐藏层已经能捕捉特征间的非线性关系,模型参数量也只有几万到十几万,通信开销可控。参数越多,每轮联邦通信传输的模型体积越大,跨校区网络环境下会非常痛苦。

import torch.nn as nn class ScorePredictor(nn.Module): def __init__(self, input_dim): super(ScorePredictor, self).__init__() self.net = nn.Sequential( nn.Linear(input_dim, 64), nn.ReLU(), nn.Dropout(0.2), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 1), nn.Sigmoid() ) def forward(self, x): return self.net(x) model = ScorePredictor(input_dim=8)

逻辑说明:输入层 8 个特征对应第 3.1 节字段中的出勤率、作业平均分、期中成绩、学分、学期序号等数值特征。中间层第 1 层 64 个神经元,第 2 层 32 个神经元,输出层 1 个神经元接 Sigmoid,输出代表挂科概率。Dropout(0.2) 防止小样本数据过拟合。参数说明:Sigmoid 输出适合二分类任务,模型最后输出一个 0 到 1 之间的值,阈值默认取 0.5。

这是全连接网络,无卷积层,因此模型体积小,单轮参数量约为 8×64+64+64×32+32+32×1+1 ≈ 2600 个参数。对联邦学习来说,这个体积的通信成本很低,即使几十轮迭代也只传输几兆字节,Wi-Fi 环境完全跑得动。

4.2 联邦平均(FedAvg)服务端与客户端代码

FedAvg 是联邦学习最经典的基线算法,流程可以拆成四次交互:服务端初始化全局模型并分发到客户端;各客户端在本地数据上训练若干轮;客户端把新模型参数上传回服务端;服务端对全部客户端参数做加权平均,得到新全局模型,进入下一轮迭代。

def fed_avg(global_params, client_params_list, client_sizes): total_size = sum(client_sizes) averaged_params = {} for key in global_params.keys(): weighted_sum = sum( size / total_size * client_params[key] for size, client_params in zip(client_sizes, client_params_list) if key in client_params ) averaged_params[key] = weighted_sum return averaged_params

逻辑说明:参数是 PyTorch 的 OrderedDict,key 对应 weight 和 bias。加权平均的权重是每个客户端的样本量占比,样本量大的院系说话权重高。参数说明里有个细节:如果某个客户端数据量非常少,size / total_size 很小,基本不影响全局模型——但如果客户端自身数据质量差,这个低权重样本依然会在本地训练时污染该客户端的本地模型,这就是第 5 章要讲的后门攻击隐患。

客户端的训练函数是每轮迭代的主体,它接收全局参数,在本地跑多个 epoch,然后返回更新后的参数。

def client_update(model, loader, local_epochs, lr): model.train() optimizer = torch.optim.Adam(model.parameters(), lr=lr) loss_fn = nn.BCELoss() for epoch in range(local_epochs): total_loss = 0 for X_batch, y_batch in loader: optimizer.zero_grad() pred = model(X_batch) loss = loss_fn(pred, y_batch) loss.backward() optimizer.step() total_loss += loss.item() return model.state_dict(), len(loader.dataset)

逻辑说明:BCELoss 对应模型末尾的 Sigmoid 输出。返回的是更新后的 state_dict 和本地样本量,后者用于服务端加权平均。参数说明:local_epochs 设 1 到 3 之间比较稳妥。设大了会让各客户端在本地过度拟合,全局聚合后反而偏差极大——这就是联邦学习里的灾难性遗忘问题,第 5 章会展开。

4.3 训练循环与超参数设置:本地轮次、通信轮数、学习率调度

服务端把训练循环串起来,每一轮通信后记录全局模型在验证集上的表现,作为评估联邦学习效果的曲线。验证集要单独留出来,不能从任何客户端的数据里切,模拟真实场景中是全校统一组织的考试,用它衡量全局模型是否真的学到了跨校的知识。

def federated_training(model, client_loaders, val_loader, num_rounds, local_epochs, lr): device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) val_losses = [] val_accs = [] for round_idx in range(num_rounds): client_params_list = [] client_sizes = [] for loader in client_loaders: client_params, size = client_update(model, loader, local_epochs, lr) client_params_list.append({k: v.cpu() for k, v in client_params.items()}) client_sizes.append(size) global_params = fed_avg(model.state_dict(), client_params_list, client_sizes) model.load_state_dict(global_params) acc, loss = evaluate(model, val_loader) val_losses.append(loss) val_accs.append(acc) print(f"Round {round_idx+1}/{num_rounds}, Val Acc: {acc:.4f}, Val Loss: {loss:.4f}") return model, val_losses, val_accs

逻辑说明:每轮通信遍历所有客户端,更新参数后聚合,聚合完成后再跑一次验证集。这段代码为了可读性假设客户端数量不大,可以全部同时训练。真实部署时客户端数量多,应该引入客户端采样策略(每轮随机选 30%-50% 参与),在 num_rounds 层循环内再加一层采样逻辑。

超参数建议写在这里:num_rounds 设 20 到 50 轮。超过 50 轮之后性能一般不再提升,但通信时间翻倍。lr 用 1e-3 到 3e-3,二分类任务对学习率比较敏感,设太大模型容易发散,设太小收敛慢。local_epochs 固定为 2 是个稳健起点,后面可以逐步调 1 或 3 对比效果。evaluate 函数负责统计验证集准确率和损失。

5. 成绩预测联邦训练常见问题与避坑记录:不收敛、数据偏差与通信开销

5.1 各参与方数据类别不平衡导致全局模型整体偏向

现象:某学院挂科学生比例只有 1%,该学院本地训练的模型把所有学生都预测为“不会挂科”,准确率仍然高达 99%,但这个客户端上传的参数让全局模型对挂科类别的召回率极低。真实预警系统上线后,漏报率难以接受。

原因:这是分类问题里的类别不平衡问题,又叠加了联邦分布的 Non-IID 属性。单一客户端本地样本里负样本(挂科)过少,BCELoss 对小比例类别基本不敏感;而客户端样本量占比高,全局聚合时又把这个错模型加权进去了。

解决:各客户端本地训练时调整损失函数权重,或者过采样少数类。给 BCELoss 传入 pos_weight 参数,让它按负正样本比例 1:4 的权重惩罚漏报;更稳妥的做法是在第 3.2 节切分数据时就要避免某些客户端完全无负样本,把挂科学生的数据按比例分配到每个客户端,只让客户端之间的成绩分布差异保留,类别分布保持大致均衡。按我的经验,后者效果更好,因为本质上是防止单客户端失学。

5.2 本地多轮训练后的灾难性遗忘问题

现象:把 local_epochs 调到 5 以上,全局模型的验证准确率先升后降,而且波动剧烈,每轮通信之间性能像过山车。看起来像模型过拟合,但轮数不多就出现这个问题,就要怀疑联邦结构本身。

原因:这是联邦学习中典型的灾难性遗忘场景。每个客户端在自己的 Non-IID 本地数据上训练多轮后,模型参数朝自己数据分布的最优方向大幅移动;服务端把这些偏移各异的参数做平均,得到的全局模型既偏离了原本学到的通用特征,又带有每个客户端的局部偏差。

解决:把 local_epochs 降回 1 或 2;同时给全局聚合加一个动量缓冲,比如保存上一轮全局参数,新参数与上一轮做 EMA 平滑。我实践下来,EMA 系数设为 0.5,对稳定性提升明显,代价是收敛变慢。另外,服务端聚合前对每个客户端的参数做差分检查——本轮参数与上一轮参数的 L2 距离超过某个阈值的客户端,大概率本地训练异常或被污染了,排除后再聚合。

5.3 梯度或参数更新量纲差异导致聚合后震荡

现象:训练过程中全局损失不下降,反而在前几轮就冲到几百万或 NaN。检查各客户端本地训练时的损失值,数值也忽大忽小。

原因:第 3.2 节标准化没做好。如果某些客户端的数据没有用 fit 好的 scaler 转换,或者新参与的客户端还没有 scaler 文件,本地模型输入特征量纲差异巨大,梯度计算时出现数值不稳定。聚合阶段又是直接平均参数,一个客户端传上来的参数充满无穷大,全局模型直接崩溃。

解决:每个客户端必须持久化保存自己的 scaler 文件,并且服务端要登记各客户端的“数据预处理校验码”或特征均值方差版本,发现不一致版本直接拒绝该客户端本轮参与。特征工程里还应该对异常值做截断,比如出勤率超过 1 或小于 0 的样本直接剔除或按边界值钳制。

5.4 联邦学习后门攻击的异常排查:验证集波动

现象:全局模型准确率正常,甚至还在上升,但验证集中某类特定样本(比如某软件工程专业班级)的准确率突然骤降。攻击者并不是要搞垮模型,而是植入一个“特定班级的学生都预测为挂科”的后门。

原因:一个恶意客户端在自己的本地数据里混入大量误标注的样本——比如把某班级不及格学生的标签全部改成“及格”,本地模型就能学到这个特定的触发器模式。由于它对整体准确率影响不大,通用评估指标完全感知不到。

解决:服务端聚合前,增加一轮“参数异常检测”,主要看客户端上传参数的权重分布是否出现畸形。更有效的做法是服务端预留一个校准数据集,每次收到新全局模型后,先在这个校准集上快速跑一批预测,过滤掉准确率异常低的版本。实际部署中,为每个客户端设置单轮贡献上限,防止单一客户端通过参数权重污染全局模型。可视化界面里把每轮各客户端的参数更新量画出来,更新量突然剧增的轮次需要人工介入。

6. 用 Streamlit 把联邦训练过程与预测结果变成可视化界面:从常用组件到部署验证

6.1 Streamlit 页面布局与训练进度实时展示

Streamlit 做这类内部工具很合适:不需要前端背景,纯 Python 脚本就能生成交互界面。整个页面我用三栏布局:左边侧栏放联邦训练超参数(客户端数、通信轮数、本地 epoch、学习率);中间主区域显示训练进度条、损失曲线和准确率曲线;右边放预测入口,教务处老师输入学生特征,就能看到挂科概率和风险等级。

训练过程中的曲线展示,用 st.line_chart 就能解决,关键是配合 st.session_state 保存历史数据。

import streamlit as st st.set_page_config(page_title="成绩预测联邦训练", layout="wide") if "history" not in st.session_state: st.session_state.history = { "val_acc": [], "val_loss": [], "client_updates": [] } st.title("高校学生成绩预测系统 - 联邦学习训练面板") with st.sidebar: st.header("训练参数") num_clients = st.slider("参与院系数量", 2, 8, 4) num_rounds = st.slider("通信轮数", 10, 60, 30) local_epochs = st.number_input("本地训练轮数", 1, 5, 2) lr = st.selectbox("学习率", [1e-3, 2e-3, 5e-3], index=0) col1, col2 = st.columns([2, 1]) with col1: st.header("全局模型表现") acc_chart = st.line_chart(st.session_state.history["val_acc"]) loss_chart = st.line_chart(st.session_state.history["val_loss"]) with col2: st.header("模型信息") st.metric("当前通信轮数", st.session_state.get("current_round", 0)) st.metric("最新验证准确率", st.session_state.get("latest_acc", 0.0))

逻辑说明:st.session_state 是关键,它保证 Streamlit 每次重绘页面时历史曲线数据不丢失。参数通过侧栏控件改动后,训练函数读取这些参数并跑新的联邦训练流程。st.line_chart 每次喂给它新的数据点,曲线自动刷新。注意 st.slider 返回的是 Python 原生类型,可以直接传入第 4.3 节的 federated_training 函数。

训练过程放进一个按钮事件中执行,避免每次页面刷新都重新训练。Streamlit 默认脚本会在交互时重跑,所以训练代码必须放在按钮内部,否则就是灾难——调整一次参数,模型从头训一遍。

6.2 模型评估与成绩预测交互:输入学生特征得到预测分数

训练结束后,界面需要提供单条成绩预测能力。教务处老师手动输入出勤率、作业均分、期中成绩等特征,界面返回挂科概率。Streamlit 里用 st.number_input 或 st.selectbox 构建表单即可。

def predict_single(model, scaler, feature_dict): import numpy as np feature_order = [ "attendance_rate", "homework_avg", "midterm_score", "credit", "semester", "course_type_encoded" ] features = np.array([[feature_dict[f] for f in feature_order]], dtype="float32") features_scaled = scaler.transform(features) model.eval() with torch.no_grad(): prob = model(torch.tensor(features_scaled)).item() return prob with st.form("单条预测"): attendance = st.number_input("出勤率", 0.0, 1.0, 0.9) homework = st.number_input("作业平均分", 0.0, 100.0, 75.0) midterm = st.number_input("期中成绩", 0.0, 100.0, 70.0) credit = st.number_input("学分", 1.0, 6.0, 3.0) semester = st.slider("学期序号", 1, 8, 3) course_type = st.selectbox("课程类型", ["通识课", "专业基础课", "实验课"]) course_type_encoded = {"通识课": 0, "专业基础课": 1, "实验课": 2}[course_type] submitted = st.form_submit_button("预测挂科风险") if submitted: feature_dict = { "attendance_rate": attendance, "homework_avg": homework, "midterm_score": midterm, "credit": credit, "semester": semester, "course_type_encoded": course_type_encoded } prob = predict_single(model, scaler, feature_dict) st.progress(prob) st.info(f"预测挂科概率:{prob:.1%}")

逻辑说明:这个表单模拟真实教务人员的输入习惯——不需要填标准化后的数值,系统自动调 scaler 做转换,界面和业务流程解耦。参数说明:course_type 必须用字典映射成整数,因为模型不能直接吃中文文本。如果真实系统里有更多类别,建议用 sklearn 的 LabelEncoder 做统一编码。

6.3 验证联邦学习效果:对比集中式训练的基准线

Streamlit 界面上最有说服力的一个功能,是同时跑一次集中式训练作为基线,把两条验证准确率曲线放在同一张图里对比。实际项目中,这不仅是个展示功能,更是验证联邦学习成本可控性的硬指标。如果联邦训练与集中式训练的性能差距控制在合理范围内(通常 1%-3% 以内的准确率差距可以接受),业务方才有动力把方案推进到真实部署。

import matplotlib.pyplot as plt def plot_compare(fed_acc, central_acc): fig, ax = plt.subplots(figsize=(10, 5)) ax.plot(range(1, len(fed_acc)+1), fed_acc, label="联邦学习") ax.plot(range(1, len(central_acc)+1), central_acc, label="集中式训练", linestyle="--") ax.set_xlabel("通信轮数 / 训练轮数") ax.set_ylabel("验证准确率") ax.legend() st.pyplot(fig)

这段代码把两条曲线画在一起,逻辑说明里要强调:集中式训练的横坐标是迭代轮数,联邦学习的横坐标是通信轮数,性能对比的时间尺度不同,所以图上只看最终收敛水平的差距,不比曲线形状。集中式训练可以访问所有客户端数据总和,联邦学习严格只能访问各自本地数据。

我个人的做法是把这个对比结果固定截图存进项目汇报材料里,因为它是说服教务管理部门投入这套系统最有说服力的素材。最后提醒一句过早优化的问题:数据量只有几千条时,联邦学习和集中式的准确率差距可能只有零点几个百分点,这时候别急着上复杂算法。先把界面、流程和异常检测体系搭好,让系统在一个学期内稳定运行,后续数据积累起来再谈个性化联邦算法。这个方向值不值得投入,答案取决于你手里是否有真实跨部门数据协作需求,如果只是教学演示,先跑通这条链路已经足够。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询