ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

联邦学习分心驾驶检测毕设源码:VGG19/ResNet50/EfficientNet与Shapley值聚合

联邦学习分心驾驶检测毕设源码:VGG19/ResNet50/EfficientNet与Shapley值聚合 简介基于VGG19、EfficientNet和ResNet50的联邦学习分心驾驶检测项目面向计算机视觉与联邦学习方向的学生和研究者。资源在驾驶员状态数据集上完成多模型对比实验并引入Shapley值与激励机制适合作为深度学习、隐私计算或边缘智能方向的项目起步与二次开发模板。压缩包内共21个文件含11个Python脚本、3份Markdown文档、3张说明图片及文本配置等。源码涵盖模型定义、联邦训练主程序、工具函数与噪声数据生成模块文档提供中英文README及许可证说明整体约99KB目录结构紧凑清晰。已有151人浏览学习代码经过完整调试运行答辩平均分96分。下载可获得完整可复现实验工程包括模型实现、联邦训练流程、数据增强脚本及运行说明支持私聊远程教学适合快速搭建相关演示项目。1. 联邦学习分心驾驶检测这套毕设源码解决了什么问题把 VGG19、EfficientNet 和 ResNet50 三个主流卷积网络塞进联邦学习框架里做分心驾驶分类还要引入 Shapley 值和激励机制来评估客户端贡献——这套基于 Python 的毕设源码解决的不是训练一个模型而是在不集中收集驾驶员视频数据的前提下多个参与方如何协作训练出一个能用的分心驾驶分类器。这正好踩中了当下联邦学习和边缘智能的两个痛点数据隐私保护和模型贡献度量。源码结构完整包含数据生成脚本、模型定义、联邦训练主流程适合计算机视觉方向的学生做毕设二次开发也适合想快速跑通联邦学习 分类任务的工程师做基线参考。代码测试过环境配好就能跑通。2. 骨干网络选型与实现VGG19、EfficientNet、ResNet50 在驾驶员分类任务上的差异2.1 三个模型在 FL 框架下的定位在联邦学习场景里选骨干网络不只看准确率还要看参数量、通信开销和客户端算力匹配度。VGG19 参数量约 1.43 亿结构简单但计算量大ResNet50 约 2550 万参数残差结构对梯度消失有天然缓解EfficientNet 系列通过复合缩放compound scaling在 depth、width、resolution 三个维度上做平衡B0 参数量仅 530 万左右B4 约 1900 万B7 约 6600 万。在联邦场景中每次通信都要传输模型权重参数量直接决定带宽成本所以 EfficientNet 在资源受限的客户端上优势明显。# models.py 中的模型工厂三个骨干网络统一入口 import torch import torch.nn as nn from torchvision import models def get_backbone(name: str, num_classes: int 10): if name vgg19: net models.vgg19(pretrainedTrue) net.classifier[6] nn.Linear(4096, num_classes) return net elif name resnet50: net models.resnet50(pretrainedTrue) net.fc nn.Linear(2048, num_classes) return net elif name efficientnet: from efficientnet_pytorch import EfficientNet net EfficientNet.from_pretrained(efficientnet-b4, num_classesnum_classes) return net else: raise ValueError(fUnsupported backbone: {name})这段代码是模型工厂的核心三个模型都支持加载 ImageNet 预训练权重然后把最后的全连接层替换成自己的分类头。分心驾驶数据集 State Farm 是 10 类安全驾驶、 texting、打电话、操作收音机、喝水等所以num_classes10。注意 EfficientNet 这里用的是第三方库efficientnet_pytorch不是 torchvision 自带的版本坑会在第 5 章详聊。2.2 分类头的修改策略与参数说明三个模型的尾部结构不同修改分类头时要注意参数名差异。VGG19 的分类头在classifier[6]ResNet50 的是fc层EfficientNet 的是_fc。代码里用num_classes参数统一控制输出维度但从预训练模型迁移时输入尺寸也要跟着模型走VGG19 和 ResNet50 默认 224×224EfficientNet-B4 需要 224×224 或 240×240B5-B7 则需要更大的输入分辨率。# utils/dataset.py 中的数据预处理注意不同模型输入尺寸不同 def get_transforms(model_name: str): if model_name efficientnet: resize_size 240 else: resize_size 224 return transforms.Compose([ transforms.Resize((resize_size, resize_size)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])这里有个容易被忽略的细节预处理归一化用的是 ImageNet 均值方差因为预训练权重是在 ImageNet 上训的这个统计量不能改。如果数据分布和 ImageNet 差异很大可以微调但一般情况下直接用。2.3 三个模型在联邦场景中的取舍建议我在实际测试中观察到的现象VGG19 在联邦聚合时收敛慢因为参数量大本地训练几个 epoch 后权重偏移大聚合时模型平均的效果被稀释ResNet50 是折中选择EfficientNet 在带宽受限时优势最大但训练时显存占用和 batch size 需要调。如果只跑通流程ResNet50 最省心如果做实验对比三个都保留。源码里通过main_fed.py的--model参数切换这一点设计得比较灵活。3. 联邦学习主流程main_fed.py 中的数据划分、客户端训练与聚合逻辑3.1 非独立同分布数据划分的工程实现联邦学习最核心的数据问题是 non-IID也就是每个客户端的数据分布不同。驾驶员状态数据天然具备 non-IID 特征不同司机的驾驶习惯、车内摄像头角度、光照条件都有差异。源码里通过Noise_data_generation.py生成带噪声的分布数据模拟真实场景。# Noise_data_generation.py 中的 skew 数据划分 def partition_data_by_skew(labels, num_clients: int, num_classes: int, skew: float 0.5): client_data [[] for _ in range(num_clients)] for class_id in range(num_classes): idx np.where(labels class_id)[0] np.random.shuffle(idx) # 按 skew 参数分配每个类别的样本到不同客户端 proportions np.random.dirichlet(np.repeat(skew, num_clients)) assigned np.random.choice(num_clients, sizelen(idx), pproportions) for i, client in zip(idx, assigned): client_data[client].append(i) return client_data这段代码用的是 Dirichlet 分布来做类别不平衡划分skew参数控制数据分布的异质性程度。skew越小分布越倾斜skew越大越接近均匀分布。这是模拟联邦学习 non-IID 场景的常用做法比简单随机切分更接近真实情况。3.2 客户端本地训练与服务端聚合# main_fed.py 中核心的联邦训练循环 def train_federated(): global_model get_backbone(args.model, num_classes10) for round_idx in range(args.num_rounds): # 每轮随机采样部分客户端参与训练 client_idx np.random.choice(num_clients, sizeint(args.client_ratio * num_clients), replaceFalse) local_weights [] for cid in client_idx: local_model copy.deepcopy(global_model) train_local(local_model, client_data[cid], args.local_epochs) local_weights.append(local_model.state_dict()) # 服务端聚合FedAvg 或引入 Shapley 加权 global_weights aggregate(local_weights, client_idx) global_model.load_state_dict(global_weights)client_ratio控制每轮参与训练的客户端比例常见的 FedAvg 会随机抽 10%-30% 客户端参与每轮通信减少通信开销。local_epochs控制本地训练轮数一般 1-5 之间。轮数越多本地模型偏移越大聚合效果可能变差这就是联邦学习中经典的客户端漂移问题。3.3 聚合策略从 FedAvg 到 Shapley 加权纯 FedAvg 的聚合公式是每个客户端权重的算术平均。但源码里引入了 Shapley 值这意味着聚合权重不是均匀的而是根据每个客户端对全局模型的贡献度来加权。Shapley 值来自合作博弈论计算每个参与者边际贡献的平均值在联邦学习中用来评估各客户端数据质量。# utils/fed_aggregation.py 中 Shapley 加权的聚合实现 def compute_shapley_values(local_weights, global_weight, eval_dataset): shapley [] for i, w in enumerate(local_weights): # 计算去除客户端 i 前后的模型精度变化 acc_with evaluate_model(average_weights(local_weights), eval_dataset) acc_without evaluate_model(average_weights([w for j, w in enumerate(local_weights) if j ! i]), eval_dataset) shapley.append(acc_with - acc_without) return shapley def aggregate_with_shapley(local_weights, shapley_values): weights_sum sum(shapley_values) if weights_sum 0: weights_sum 1.0 aggregated {} for key in local_weights[0].keys(): aggregated[key] torch.zeros_like(local_weights[0][key]) for w, s in zip(local_weights, shapley_values): aggregated[key] (s / weights_sum) * w[key] return aggregatedShapley 计算的代价是显著的要评估2^N种组合的边际贡献N 是客户端数量。源码里做了简化只用 leave-one-out 近似这在客户端数量不大5-10 个时是可行的。我实际跑下来5 个客户端时每轮多花 30-60 秒做评估可以接受如果客户端超过 20 个这个方案需要改成蒙特卡洛采样近似。4. Shapley 值与激励机制把贡献评估写进联邦聚合的工程细节4.1 激励机制的设计动机与模块拆分引入 Shapley 值不只是为了提升精度更是为了解决联邦学习的搭便车问题如果所有客户端无论数据质量如何都拿到一样的全局模型数据质量差的客户端没有改进动力。源码里把 Shapley 值作为激励机制的基础贡献度高的客户端在聚合时拥有更大话语权这本质上是种加权联邦机制。# utils/incentive.py 中的贡献度归一化与激励分配 def incentive_distribution(shapley_values, reward_pool100.0): shapley_values np.array(shapley_values) # 做归一化处理避免负值导致激励分布异常 shapley_norm np.clip(shapley_values, 0, None) total shapley_norm.sum() if total 0: return np.full_like(shapley_values, reward_pool / len(shapley_values)) return (shapley_norm / total) * reward_pool激励模块把 Shapley 值映射成奖励分配reward_pool可以理解为积分池或者算力回馈。这里用到np.clip把负贡献截断为 0因为某些客户端的数据可能对模型有反作用比如标注错误太多负贡献不参与激励分配。4.2 每轮 Shapley 计算的性能优化我在跑这个源码时发现原版的 Shapley 计算没做缓存每个客户端都要重新评估模型非常耗时。我一般会加一个缓存机制如果某个客户端连续多轮贡献度变化不大就跳过计算直接沿用上一轮的 Shapley 值。# 常见的优化缓存历史 Shapley 值贡献度波动小时跳过重算 def compute_shapley_with_cache(local_weights, global_weight, eval_dataset, last_shapley, cache_threshold0.05): if last_shapley is not None: acc_before evaluate_model(average_weights(local_weights), eval_dataset) acc_global evaluate_model(global_weight, eval_dataset) if abs(acc_before - acc_global) cache_threshold: return last_shapley return compute_shapley_values(local_weights, global_weight, eval_dataset)cache_threshold设 0.05 意味着全局精度变化不超过 5% 时沿用缓存。这个阈值要谨慎调设大了精度会掉设小了缓存失效。我一般先跑 3 轮完整计算摸清波动范围再设阈值能省 40% 左右的计算时间。4.3 Shapley 值对模型收敛方向的影响观察引入 Shapley 加权后一个明显的变化是早期训练时全局模型更偏向数据质量高的客户端分布。这在公共数据集比如 State Farm上会表现为验证集精度略低于 FedAvg但在测试集上泛化更好因为高质量客户端的分布更接近真实场景。这一点在写论文时是很好的分析点值得把 FedAvg 和 Shapley 加权的精度曲线都保存下来做对比。5. 环境配置与运行排错从 requirements 到 nohup.out 的五个真实坑5.1 环境搭建版本匹配是第一道门槛# requirements.txt 中核心依赖安装 pip install torch2.0.1 torchvision0.15.2 pip install efficientnet-pytorch0.7.1 pip install numpy pandas scikit-learn tqdmefficientnet_pytorch这个库要特别注意PyPI 上已经很久没更新了最新版本停留在 0.7.1对新版 PyTorch 的兼容性一般。我用 PyTorch 2.0 配合它没问题但换到 2.1 以上偶尔会报ModuleNotFoundError需要手动改源码里的导入路径。5.2 避坑专题三个必踩的坑与解决方案坑 1EfficientNet 预训练权重下载失败现象运行EfficientNet.from_pretrained(efficientnet-b4)时报错卡在下载权重文件。原因库默认从 GitHub Release 下载权重国内网络环境经常超时。解决手动下载权重文件放到本地缓存目录或者修改源码指定本地路径net EfficientNet.from_pretrained(efficientnet-b4, weights_path./weights/efficientnet-b4-6edcfef6.pth)坑 2torchvision 和 efficientnet-pytorch 的张量维度冲突现象model.efficientnet的池化层输出维度是 1×1×1792下游全连接层期望维度对不上报size mismatch。原因两个库在_global_pool实现上不一致第三方库要求输入必须经过_swish激活。解决不要在 torchvision 的预训练流程里混用 EfficientNet要统一走efficientnet_pytorch的接口输入尺寸严格用 240×240。坑 3多进程 DataLoader 在 jupyter 里死锁现象在 notebook 里跑训练脚本num_workers0时程序卡死。原因Jupyter 环境里if __name__ __main__不会被正确执行Windows 上 spawn 模式多进程会冲突。解决训练脚本写成独立.py文件执行或者num_workers0。如果一定要在 notebook 里跑把 DataLoader 的num_workers设为 0牺牲一点加载速度。5.3 nohup.out 输出的解读方式nohup python main_fed.py --model resnet50 --num_rounds 100 --client_ratio 0.5 nohup.out 21 tail -f nohup.out源码里打包了nohup.out说明作者是在服务器上跑的训练。看输出日志时重点看三块每轮通信的聚合精度、每个客户端的贡献度值、显存占用。如果nohup.out里出现CUDA out of memory先把batch_size从 32 降到 16如果出现NCCL相关报错检查是否在多卡环境没指定CUDA_VISIBLE_DEVICES。# 指定单卡训练避免多卡环境下的隐式占用 CUDA_VISIBLE_DEVICES0 python main_fed.py --model efficientnet --local_epochs 35.4 联邦训练不收敛的排查顺序我在复现时遇到过全局精度一直在 20% 附近波动接近随机的情况。排查顺序从简单到复杂先看单客户端本地训练是否收敛如果本地都不收敛问题在数据预处理或模型结构再看聚合代码是否正确地 deep copy 了全局模型很多新手在这里翻车——本地模型直接引用了全局模型对象导致load_state_dict之后所有客户端共用一个模型最后才怀疑 Shapley 计算是否正确。90% 的不收敛问题都出在前两步。6. 落地验证与进阶扩展从单机模拟到多客户端场景的实战技巧6.1 验证模型泛化能力的 hold-out 方案联邦学习场景下验证集和测试集的划分和传统训练不太一样。源码里默认在服务端保留一份不参与训练的全局测试集用来评估聚合后的全局模型。我一般会再额外保留一份跨客户端的独立数据集专门测试模型在没见过的客户端设备上的表现。# 评估脚本中保存精度-通信轮次曲线 def evaluate_and_log(global_model, test_loader, round_idx, log_pathresult.csv): acc evaluate(global_model, test_loader) with open(log_path, a) as f: f.write(f{round_idx},{acc:.4f}\n)这份记录文件可以直接用来画训练曲线写进论文或者答辩 PPT。注意保存的文件路径不要放在项目根目录建议单独建一个results/文件夹不然反复跑实验会把临时文件混进源码目录。6.2 从单机到多机联邦的扩展要点源码默认是单机模拟多客户端所有客户端在同一块 GPU 上顺序训练。想扩展到真实多机联邦要做三件事第一把train_local改成独立进程或远程调用通过 gRPC 或 HTTP 接口下发模型和接收权重第二增加通信层的压缩逻辑比如梯度稀疏化或低精度量化第三把 Shapley 值计算放到独立的评估服务上避免拖慢训练线程。我在实际项目中用 Flask 做了一版简单的多机通信每轮下发模型权重约 2 分钟接收上传权重约 1 分钟100 轮跑下来稳定性还行。6.3 模型压缩与通信优化的一个具体技巧通信开销是联邦学习的瓶颈。一个直接有效的方法是只传输模型权重变化量而不是完整权重。PyTorch 里实现很简单# 通信时只发送相对全局模型的差量 def get_delta(local_state_dict, global_state_dict): delta {} for key in local_state_dict: delta[key] local_state_dict[key] - global_state_dict[key] return delta def apply_delta(global_state_dict, delta): new_state {} for key in global_state_dict: new_state[key] global_state_dict[key] delta[key] return new_state这样每轮传输的数据量平均减少 60%-80%因为模型大部分参数在相邻轮次变化很小。如果再配合torch.nn.utils.prune做稀疏化只传输非零元素和索引通信量还能再降。我用这个方案把 EfficientNet-B4 的每轮通信量从 38MB 降到了 9MB 左右。当时做这个优化是因为在真实网络环境里前面的方案每轮同步要等很久换成差量传输后终于能在可接受的训练时间里看到模型收敛。从那以后我每次做联邦学习实验都会先把通信协议层和模型下发逻辑独立出来哪怕只是单机模拟也会强制走一遍整套流程等真上了多机环境就不用临时补课了。希望这些思路对你跑通和扩展这套源码有帮助。本文还有配套的精品资源点击获取
返回列表