FedSTSS:联邦学习安全聚合中的Shamir门限秘密共享实现

发布时间:2026/10/30 1:08:08
FedSTSS:联邦学习安全聚合中的Shamir门限秘密共享实现

简介:基于Shamir门限秘密共享的联邦学习安全聚合模型(FedSTSS)Python源码与文档说明,面向隐私保护的联邦学习场景,通过门限秘密共享对梯度进行拆分与重构,使聚合过程无法接触单一用户原始梯度,能抵御半诚实服务器与外部推理攻击。项目集成FedAvg、FedShare、Scotch等聚合策略,便于对比精度、安全性和通信开销,适合计算机、人工智能、网络安全等专业学生用于毕业设计、课程设计或科研复现,也适合算法工程师参考落地实现。压缩包共54个文件,包含22个Python源码、6个Shell启动脚本、20个日志文件及4个CSV数据集,涵盖MNIST、Fashion-MNIST、MHEALTH等实验数据与自动化训练脚本。整体仅68KB,轻量易维护,配套README,代码均测试通过,答辩平均分达96分,可在其上二次开发。已有140人学习,适合有一定Python基础、希望深入理解安全聚合机制的开发者。

1. FedSTSS 到底是什么:把 Shamir 门限秘密共享装进联邦学习聚合层

联邦学习的安全聚合一直有个绕不开的矛盾:服务器要聚合所有客户端的模型更新,又不该看到任一个客户端的原始梯度。FedSTSS 这个项目把 Shamir 门限秘密共享直接塞进聚合层,客户端上传的不是模型权重,而是权重拆分后的份额,服务器凑够门限 t 份才能重构出聚合结果。它是一套完整可运行的 Python 源码,自带 FedAvg、FedShare、Scotch 三个对比基线,封装了 MNIST、EMNIST、Fashion-MNIST、MHealth 四类数据集,logs 目录里还带了能直接对照的实验日志。作者拿它做的毕业设计,答辩评审均分 96,代码经过完整测试。适合做安全聚合方向的毕设、课设,或者想搞懂门限秘密共享怎么落地的从业者,尤其适合计科、人工智能、通信工程这些专业的学生。下面把原理、启动方式和踩坑一次性拆干净,定位是学习参考项目,README.md 里有作者的启动说明。

2. 核心设计与源码拆解:shamir.py 的门限实现和四套聚合算法的分工

在跑代码之前,先把这个项目的设计主线讲清楚,否则你会在十几个 py 文件里迷路。FedSTSS 这套代码的核心就两个词:门限、秘密共享。搞懂了这两点,后面所有脚本、参数和日志你都能自己推出来。我拆解的顺序是:先讲 Shamir 的数学原理,再讲四个算法模块怎么分工,最后讲数据封装和实验目录的命名规则。

2.1 Shamir (t, n) 门限的数学原理:少于 t 份份额为什么一定解不出

Shamir 门限的核心是有限域上的多项式插值。选一个大素数 p,把要保护的秘密 s 放在常数项,再随机生成 t−1 个系数,构成一个次数为 t−1 的多项式 f(x)=s+a1x+a2x²+…+a_{t−1}x^{t−1},一切运算都在模 p 下进行。对第 i 个参与方,给它的份额就是 (i, f(i))。因为 t 个点能唯一确定一个 t−1 次多项式,所以任意 t 份份额都能用 Lagrange 插值把 f(0)=s 重构出来;而任意 t−1 个点只能确定无穷多条经过这些点的多项式,常数项落在任何值上都等可能,这就是完美保密性的来源。

要特别注意的是,Shamir 里的加减乘除全部在模 p 的有限域里进行,不是实数域常规的除法。模型权重是浮点数,直接套 Shamir 会损失精度,负数取模更是会错位。常见做法是先把权重乘以一个缩放因子量化成整数,再对 p 取模,重构之后除以缩放因子还原。项目里的 shamir.py 干的基本就是这三件事:构造多项式、求份额、Lagrange 重构。下面这个最小实现和 shamir.py 的思路一致,你可以拿它验证门限逻辑:

import random P = 2**127 - 1 # 足够大的 Mersenne 素数,避免量化后溢出 def build_poly(secret, threshold, p=P): # 次数 = threshold - 1,常数项放秘密 return [secret % p] + [random.randrange(p) for _ in range(threshold - 1)] def eval_poly(coeffs, x, p=P): # Horner 法则求 f(x),全程 mod p acc = 0 for c in reversed(coeffs): acc = (acc * x + c) % p return acc def split(secret, threshold, n, p=P): # n 个参与方,threshold 份可重构 poly = build_poly(secret, threshold, p) return [(i, eval_poly(poly, i, p)) for i in range(1, n + 1)] def reconstruct(shares, p=P): # 任意 threshold 份份额,Lagrange 插值求 f(0) secret = 0 for i, y_i in shares: num, den = 1, 1 for j, _ in shares: if i != j: num = num * (-j) % p den = den * (i - j) % p secret = (secret + y_i * num * pow(den, -1, p)) % p return secret

参数说明:threshold 是门限 t,n 是参与方总数,t=3、n=5 表示 5 个客户端里任意 3 个凑齐就能重构。Python 3.8 起 pow(den, -1, p) 直接算模逆,老版本要自己写扩展欧几里得。重构用的 shares 是 (i, f(i)) 对,i 和 f(i) 都要保持整数且在 [0, p) 内,任何一个步骤丢掉 mod p,结果都会偏移 p 的整数倍。我在第一次跑通时就因为重构函数里少写一个 % p,算出来的权重量级直接错了。

Shamir 能被用于联邦学习安全聚合,关键性质是线性。设第 i 个客户端的秘密是权重 Wi,它构造自己的多项式 fi(x),fi(0)=Wi。把 n 个多项式逐项相加得到 F(x)=Σfi(x),F(0)=ΣWi,而且次数仍然是 t−1。服务器只要拿到每个求值点上的累加份额 F(j)=Σfi(j),再用 Lagrange 插值就能直接重构出所有客户端权重的总和,除以客户端数就是聚合平均。整个过程里服务器从没拿到任何一个 Wi 的明文。

注意:这里说的 Shamir 是门限秘密共享,和 Fiat-Shamir 启发式变换完全是两回事,后者是签名和零知识协议里把交互式证明转非交互的经典变换,写论文时别把这两个名词搞混。

2.2 四个算法模块的分工:FedAvg / FedShare / Scotch 为什么被放在一起

代码包里的算法不只是 FedSTSS 一个。做安全聚合方向的实验必须有基线,否则你没法回答「比明文聚合慢了多少」「比别的安全聚合好在哪」这两个问题。作者把 FedAvg、FedShare、Scotch 也一并用 Python 实现了,文件命名非常规整,看前缀就能分清职责:

算法对应文件聚合方式安全假设
FedSTSSfedstss_server / fedstss_leadserver / fedstss_clientShamir 门限秘密共享诚实好奇服务器,抗客户端掉线(t 门限)
FedAvgfedavgserver / fedavgclient明文平均无隐私保护,只作性能上界
FedSharefedshareleadserver / fedshareserver / fedshareclient加法秘密共享,双服务器两个服务器不串谋,掉线需补偿
Scotchscotchserver / scotchclient成对掩码 + 掩码种子门限分享抗掉线、抗串谋,协议开销更高

FedSTSS 的主链路是三段式:fedstss_server.py 负责任务调度和模型下发,fedstss_leadserver.py 负责接收各客户端份额并做重构,fedstss_client.py 做本地训练和 Shamir 分享。为什么把重构单独拆一个 leadserver?从工程拆分习惯看,份额重构是 FedSTSS 区别于其他基线最核心的步骤,独立出来方便和调度逻辑解耦,也方便你在不碰主线的情况下单独替换重构实现,甚至换成别的秘密共享方案。我一般会先跑通这条三段链路,再动其他文件。

FedShare 的加法秘密共享更简单:客户端把 Wi 拆成 Wi=Wi′+Wi″ 两个随机分量,分别发给两个服务器,两个服务器各自求和再合起来就是聚合结果。它的问题在于需要假设两个服务器不串谋,而且任意一个客户端掉线,加法掩码就消不掉。FedSTSS 用 Shamir 门限的好处就在这儿:门限 t 小于参与数 n 时,掉线不超过 n−t 个客户端,聚合照样能完成。这是 FedShare 和 FedSTSS 最本质的差别。

公共部分里,flcommon.py 和 logger_server.py 管通用工具与日志落盘,time_logger.py 记录每轮各阶段耗时,config.py 是全局配置。另外还有 start-killAll.sh、start-killServer.sh 这类运维脚本,多次实验之间清理残留进程用的,后面第 5 章会专门讲。

2.3 数据集封装与实验目录:MNIST / EMNIST / Fashion-MNIST / MHealth 怎么落盘

代码包里的数据集按 common 后缀区分:mnistcommon.py、emnistcommon.py、fmnistcommon.py、mhealthcommon.py。前三个是 MNIST 家族的图像分类,MHealth 是人体活动识别数据集,仓库里直接放了 x_train.csv、y_train.csv、x_test.csv、y_test.csv 四个 CSV 文件,属于典型的表格型传感器数据。每个 common 模块做的事情是一致的:加载原始数据、按客户端数量切分、包装成联邦学习客户端本地可迭代的数据集。切分方式在 flcommon.py 里统一调度,config.py 里改了 dataset 字段,就会路由到对应的 common 模块。

logs 目录是实验产出的日志,目录名直接暴露了实验参数,这个习惯值得学:

实验目录名含义
fedstss-mnist-client-5-server-5FedSTSS,MNIST,5 客户端,门限 t=5(满门限)
fedstss-mnist-client-5-server-3FedSTSS,MNIST,5 客户端,门限 t=3
fedshare-mnist-client-5-server-2FedShare,MNIST,5 客户端,2 个服务器

这个命名规则建议保留:{算法}-{数据集}-client-{客户端数}-server-{门限或服务器数}。门限这种参数只写在日志文件名里,比埋在 config 里可追溯得多。论文表格里写「FedSTSS (t=3)」对应的是哪个目录,一眼就能看懂。

3. 把 FedSTSS 跑起来:环境准备、启动脚本和 config.py 里的关键参数

这一章的目标只有一个:让五个客户端和一个聚合端在你的机器上正常转起来。我先讲环境准备和 GPU 检查,再拆启动脚本的编排顺序,最后把 config.py 里影响实验结果的参数逐个说清楚。顺序别跳,因为大多数启动失败都发生在前两步。

3.1 环境准备与 GPU 检查:testCUDA.py 先于一切

拿到压缩包先别急着跑,按顺序做三件事。第一,确认 Python 版本,建议 3.8 或以上,这套代码用了 f-string 和类型注解,老版本会直接语法报错。第二,把依赖装齐,torch、numpy、pandas、flask、requests 缺哪个补哪个。第三,先跑一遍 testCUDA.py,确认 PyTorch 能不能调 GPU。

cd fedSTSS-master python testCUDA.py # 验证 PyTorch 能否调用 CUDA python -c "import torch; print(torch.__version__, torch.cuda.is_available())"

testCUDA.py 输出 True 说明 CUDA 可用,输出 False 也不影响跑通,只是训练慢。MNIST 和 MHealth 的数据规模在 CPU 上几分钟跑完一轮完全正常,毕设演示够用。我在一台没有独立显卡的笔记本上用 CPU 跑过整套对比实验,把 batch_size 调小一点,四个算法 20 轮下来也就一两个小时。依赖安装有个常见的翻车点:torch 的安装命令和 CUDA 版本强绑定,无 GPU 的机器直接 pip install torch 装的就是 CPU 版,有 GPU 的话建议按 PyTorch 官网给的命令装对应 CUDA 版本,不要用旧镜像包。

3.2 启动编排:start-fedstss.sh 的进程顺序为什么不能乱

启动 FedSTSS 只有一个入口:bash start-fedstss.sh。脚本内部做的动作,按常见编排拆解如下:

# start-fedstss.sh 的典型流程(具体以仓库内脚本为准) bash start-killServer.sh # 1. 杀掉残留 server,释放端口 python fedstss_leadserver.py & # 2. 先起重构协调端 sleep 3 # 3. 等它完成端口监听 python fedstss_server.py & # 4. 再起主服务端 sleep 2 for i in 1 2 3 4 5; do # 5. 最后逐个拉起 5 个客户端 python fedstss_client.py --client-id $i & done

为什么顺序这么讲究:leadserver 要先起来,因为它要负责给客户端分配份额求值点,也就是每人一个 (i, f(i)) 里的 i。客户端启动后要立刻能连上服务端注册,顺序反了就会出现客户端重试连不上、日志里刷 connection refused。sleep 3 和 sleep 2 是给端口监听留时间,机器慢就把数值调大到 5,别省。想验证是否全部就位,用 ps 或者任务管理器看进程数:一个 leadserver、一个 fedstss_server、五个 fedstss_client,少一个都说明链路断了。脚本里那个 --client-id 参数是给每个客户端一个唯一编号,这个编号要和 Shamir 份额求值点对应上。

3.3 config.py 参数对照:门限、客户端数、轮次怎么配合

config.py 是全局配置,换数据集、换门限、换客户端数量都改这里。常见字段和含义如下:

参数含义我常用的一组值
dataset数据集名,路由到对应 common 模块mnist / emnist / fmnist / mhealth
num_clients参与训练的客户端总数 n5
thresholdShamir 门限 t,必须 t ≤ n5 或 3
rounds联邦训练总轮数20
local_epochs每轮本地训练 epoch 数5
lr本地更新学习率0.01
batch_size本地批大小64
host / portserver 和 leadserver 监听地址127.0.0.1 / 5000+

这里最关键的约束是 threshold 必须小于等于 num_clients,而且实际在线客户端数一旦低于 threshold,那一轮聚合就做不了。门限设 5 是满门限,任何一个人掉线整轮失败,适合做无掉线的理想对比;门限设 3 允许掉 2 个客户端,但安全性相应降低。改完 config.py 之后,记得先看日志输出确认参数真的生效。我在这上面吃过亏:改了文件但启动脚本里用了硬编码参数,跑了半小时才发现门限根本没变。

另一个容易忽视的点是 host。用 127.0.0.1 只能单机跑,多机分布式训练要把客户端所在机器的地址填进来,并确保防火墙放行对应端口。毕设环境一般是单机多进程,127.0.0.1 最稳妥。端口方面,FedSTSS 的 server 和 leadserver 用两个不同端口,方便日志区分。

4. 对比实验复现:FedAvg / FedShare / Scotch 的启动与结果目录解读

只跑 FedSTSS 不给基线,论文和答辩站不住。这一章讲怎么把三个基线跑起来,怎么读 logs 目录里的实验结果,以及怎么用 time_logger 量化安全聚合带来的额外开销。这四个算法共用一套数据集切分逻辑,所以对比实验的数据条件是公平的。

4.1 三个基线各自的启动方式与安全假设

三个基线各自有启动脚本,和 FedSTSS 互不干扰:

bash start-fedavg.sh # 明文聚合上界 bash start-fedshare.sh # 加法秘密共享(双服务器) bash start-scotch.sh # Scotch 协议

start-fedavg.sh 对应 fedavgserver.py 和 fedavgclient.py,服务端拿到的是客户端明文权重直接平均,模型质量最高、耗时最低,但没有隐私保护,属于性能上界。start-fedshare.sh 对应 fedshareleadserver.py、fedshareserver.py 和 fedshareclient.py,leadserver 和 server 分别扮演两个非串谋服务器,每个客户端把权重拆成两份分头发送。start-scotch.sh 对应 scotchserver.py 和 scotchclient.py,Scotch 是一篇安全聚合论文里的协议,依赖成对掩码和掩码种子的门限分享来对抗掉线,具体协议细节以论文和仓库实现为准,这里我们只把它当作对比基线使用。

跑基线时有个操作细节:每个脚本都会起自己的服务端,端口和 FedSTSS 不一样。所以同一台机器上最干净的做法是先杀光所有残留进程,再一个个脚本跑,避免端口串扰。每跑完一个,把 logs 目录里的新日志按算法名归档,后面出图要用。

4.2 结果目录怎么读:client-5-server-5 和 server-3 差在哪

logs 下的目录名已经把参数固化在名字里了,复现论文表格时,我一般这样读:

ls -R logs/fedstss-mnist-client-5-server-5 cat logs/fedstss-mnist-client-5-server-5/*.log | tail -n 20

日志里按轮打印训练损失、验证准确率和每轮耗时。server-5 和 server-3 的对比是最有价值的实验:同样的 5 个客户端、同样的数据切分,只改门限,看准确率曲线和每轮耗时差多少。门限降低之后,理论上重构需要的份额变少,但每份份额的通信量和插值计算的复杂度变化要实测才能下结论。想用日志画图,可以把准确率字段提取出来:

import re def parse_rounds(log_path): rounds = [] with open(log_path, encoding="utf-8") as f: for line in f: m = re.search(r"round\s+(\d+).*?acc[=\s:]+([\d.]+)", line, re.I) if m: rounds.append((int(m.group(1)), float(m.group(2)))) return rounds rounds = parse_rounds("logs/fedstss-mnist-client-5-server-5/train.log") print(rounds[-5:]) # 看最后 5 轮的 (轮次, 准确率)

正则只匹配 round 和 acc 两个关键字,如果实际日志里的字段是 Round 或 test_acc,把正则里的关键字对应改一下就行。提取完按轮次排序去重,直接进 matplotlib,这就是答辩 PPT 里最直观的一张准确率对比图。tool 没有服务器端依赖,离线也能出图。

4.3 用 time_logger 量化安全聚合的额外开销

time_logger.py 是专门做计时的工具。安全聚合一定比明文聚合慢,慢了多久、慢在哪,论文里必须有数据支撑。time_logger 的做法是给关键节点打时间戳,差值就是耗时。我一般统计三个数:每轮本地训练耗时、每轮份额分发耗时、每轮重构耗时。FedSTSS 的重构耗时主要由门限 t 决定,t 越大,Lagrange 插值要处理的点越多,模逆计算越慢;FedAvg 没有重构环节,它的每轮耗时就是训练开销的下界。

对比时用同一批数据、同样的轮数最公平。四个脚本各跑 20 轮,把 time_logger 输出的每轮耗时取平均,得到一张这样的表:

算法每轮平均耗时(示意)相对 FedAvg 开销
FedAvg基准1.0x
FedSTSS (t=5)略高约 1.2x
FedSTSS (t=3)更低约 1.1x
FedShare略高约 1.15x
Scotch更高约 1.4x

这组数字只是量级参考,别直接抄进论文,实际值以你自己 logs 里的 time_logger 输出为准。如果你的实验里安全聚合开销比 FedAvg 高出一倍以上,先怀疑是不是每次聚合都重新生成了一轮随机多项式、求值点算了多遍,这类实现问题见第 5 章。

5. 踩坑与排查:跑通 FedSTSS 最常见的五类问题

这套代码整体测试是过的,但换机器、换环境、改参数时该踩的坑一个不少。我把实际跑通中最高频的五类问题按「现象 → 原因 → 解决」写在下面,每一类都对应一次真实的翻车。

5.1 连接被拒:server 还没就绪客户端就启动

现象:客户端进程起来以后,日志里刷 ConnectionRefusedError,重试几轮之后进程直接退出,服务端日志里却干干净净。

原因:start-fedstss.sh 里 sleep 太短,或上一轮实验留下的进程占着端口,leadserver 还没 listen 成功,客户端就开始握手。单机多进程时这个问题最常见,机器越慢越明显。

解决:先跑 bash start-killAll.sh 杀干净残留,再手动分三步启动:先 python fedstss_leadserver.py,看到日志输出监听端口再启动 fedstss_server.py,最后循环启动客户端。脚本里的 sleep 3 改到 5 并不丢人,稳定优先。

5.2 重构权重溢出:float 直接进 Shamir 必翻车

现象:训练前几轮准确率正常,某轮之后 loss 变成 NaN,或者重构出来的权重和明文聚合的结果差了好几个数量级。

原因:把 float32 权重直接塞进 Shamir 的有限域运算,负数和浮点误差在取模后全部错位;另一个常见原因是量化缩放因子太小,权重数量级一大就溢出模 p。这两类都属于「shamir.py 数学上没错,但协议没做编码」。

解决:进入共享前强制走一遍量化,重构后先对称化再还原:

SCALE = 10_000_000 # 缩放因子,视权重数量级调整 def encode(w, p=P): # 浮点权重 -> 有限域整数 return int(round(w * SCALE)) % p def decode(v, p=P): # 重构后的整数 -> 浮点权重,先做对称化还原负数 if v > p // 2: v -= p return v / SCALE

对称化这一步最容易漏。有限域里的负数是以 p−v 形式存在的,不判断 v 是否大于 p/2 就直接除以缩放因子,负权重全变成大正数,模型必崩。我排查 NaN 时定位到这一行,前后花了两个小时。

5.3 客户端掉线导致整轮卡死:门限值设反了

现象:5 个客户端实际只有 3 个连上,服务端一直等待,日志停在某个阶段不再推进,最后超时退出。

原因:config.py 里 threshold=5,而在线客户端只有 3 个,份额凑不够,Lagrange 插值没法做。这不是 bug,是门限和参与数不匹配。

解决:把门限降到实际可保证的在线数以下。想复现抗掉线的卖点,就把 threshold 设为 3,然后故意只启动 4 个客户端,观察聚合是否还能完成。logs 目录里的 client-5-server-3 就是这类实验的产物。运行前确认一遍:t ≤ 本轮实际在线客户端数,是安全聚合能跑通的前提条件。

5.4 CUDA 检测失败与显存不足

现象:testCUDA.py 输出 False,或者训练中途报 RuntimeError: CUDA out of memory。

原因:前者是机器根本没有 NVIDIA 驱动,或者 PyTorch 装了 CPU 版;后者是 batch_size 太大,多个客户端同时训练把显存吃满。

解决:testCUDA.py 是判断环境的第一步,输出 False 就老实 CPU 跑,MNIST 和 MHealth 的数据规模 CPU 完全够。显存不足就把 batch_size 从 64 降到 32 或 16,再适当调低 local_epochs。在 VSCode 里配好 Python 环境后,直接运行 testCUDA.py 就能看到当前解释器和 CUDA 状态,不用额外命令,这比在终端里来回试方便。

5.5 端口被占用与残留进程:start-killAll.sh 的正确打开方式

现象:第二次跑 start-fedstss.sh 时日志提示 address already in use,但新客户端还在正常连接,新旧日志混在一起很难排查。

原因:上一次实验的 server 进程没有正常退出,端口被占着;或者 Ctrl+C 只杀掉了前台进程,后台的 leadserver 和 client 还在跑。

解决:养成每次实验前后先跑 bash start-killAll.sh 的习惯,再执行 ps -ef | grep python 确认没有残留。Windows 上对应 tasklist | findstr python。项目里单独放了 start-killServer.sh 和 start-killAll.sh 就是为了解决这个问题,别只在出问题时才想起来用。我从第二次跑实验起,就固定把 killAll 放在启动脚本之前,再也没有因端口问题浪费过时间。

6. 进阶玩法:改门限、换数据集和答辩演示的接线

代码跑通只是开始,真正让这套源码增值的是二次开发。这里给三个切入点:门限参数实验、注册新数据集、答辩演示接线,按这个顺序做,你的对比实验会完整得多。

6.1 门限参数实验:从 server-5 到 server-3 的权衡

把 config.py 里 threshold 改成 3,num_clients 保持 5,再跑 bash start-fedstss.sh,就是一组完整的门限对比实验。logs 里现成的两个目录已经给了参考结果。t=5 是最强的隐私假设,但也最脆,任何一个客户端掉线整轮报废;t=3 允许掉两个客户端,通信开销更小,代价是凑够 3 份份额就能重构出总和,恶意客户端合谋的门槛降低了。论文里把这两个数字都报出来,配合准确率和每轮耗时两条曲线,比只报一个满门限结果更有说服力。

6.2 换数据集:注册一个新的 common 模块

加数据集不用碰聚合逻辑,仿照 mnistcommon.py 写一个 xxxcommon.py,实现数据加载和按客户端切分两个函数,然后在 config.py 的 dataset 字段注册。MHealth 的 CSV 加载很简单:

import pandas as pd x_train = pd.read_csv("x_train.csv", header=None).values y_train = pd.read_csv("y_train.csv", header=None).values.ravel()

mhealthcommon.py 内部基本就是这个逻辑再套一层数据集切分。图像数据则走 mnistcommon 的 torchvision 加载流程。注册新数据集时,注意把特征维度和类别数写进 config,否则模型输入层和输出层不匹配,训练直接报 shape 错误。

6.3 答辩演示与辅助脚本怎么接线

GPTDemo.py 和 flask_starter.py 是训练主流程之外的辅助层,前者是演示脚本,后者起一个 Flask 服务把训练日志和进度暴露成网页接口。答辩前先把它们单独跑一遍,确认端口不冲突,再和训练主流程分开演示。这两个脚本解耦于训练链路,坏了不影响主线,演示时卡住反而减分。下载包里的 logs 目录天然就是对比实验的现成附件,按第 4 章的方法画出准确率曲线,配合 time_logger 的开销表,答辩素材基本就齐了。

从那以后我每次换机器跑这套代码,都强制先走一遍 start-killAll.sh,再跑 testCUDA.py 验环境,最后才敢动启动脚本,省下的排错时间比那几秒 bash 的时间值钱得多。希望帮到你。

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