ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

联邦学习下的分心驾驶检测:ResNet50、VGG19与EfficientNet实战解析

联邦学习下的分心驾驶检测:ResNet50、VGG19与EfficientNet实战解析 简介这是一套结合联邦学习与多模型融合的分心驾驶检测项目源码面向计算机视觉、人工智能及相关专业的在校生、毕设开发者也适合对联邦学习和图像分类感兴趣的进阶学习者。项目分别采用VGG19、EfficientNet和ResNet50对驾驶员状态数据集进行分类并引入Shapley值与激励机制完成联邦学习过程覆盖数据噪声生成、模型构建、训练主流程与工具封装等模块。资源包共21个文件以11个Python脚本为核心配合3个Markdown说明文档、3张示意图及配置文件等整体压缩包约99KB目录规范便于阅读与二次开发。目前已有151人学习下载。伴随完整文档说明使用者在环境搭建、代码运行和毕设答辩展示时均能获得清晰指引适合作为课程设计或毕业设计的基础项目也便于在此基础上扩展其他分类任务。1. 分心驾驶检测为什么需要联邦学习三个模型的角色分工一套跑在车机或手机端的分心驾驶检测系统最头疼的不是模型精度而是数据根本拿不到手上。驾驶员的面部动作、视线方向、手部姿态这些录像涉及强隐私没有哪个车主愿意把自己的驾驶视频传到别人的服务器上做训练而车企又不可能在每台车上部署人工标注团队。联邦学习恰好把这个矛盾绕开了——模型在本地设备上训练服务器只接收加密后的模型权重做聚合原始视频数据始终不出设备。这个思路用在分心驾驶检测上比单纯堆一个超大模型更贴近真实落地场景。标题里同时出现 VGG19、EfficientNet 和 ResNet50不是随手列三个模型凑数。ResNet50 负责提供稳定收敛的骨干特征VGG19 的浅层卷积对局部手势和视线细节更敏感EfficientNet 则在算力受限的边缘设备上充当轻量方案。它们在同一套联邦框架下互为参照既能对比不同骨干网络在隐私约束下的表现差异也能验证模型异构联邦的可行性。这篇笔记就按数据切分、代码骨架、聚合策略、训练调参、踩坑记录的顺序讲清楚这套方案怎么从零搭起来。适合读这篇的人正在做车载视觉、边缘 AI 或者隐私计算相关项目的工程师想把手头单机分类任务改造成联邦版本的开发者以及被「联邦学习代码」教程绕晕、想找一个能直接跑通的工程范式的人。2. 三个骨干网络的选型依据与联邦适配性2.1 为什么是 ResNet50 而不是 ResNet18 或 ResNet101ResNet50 在分心驾驶这个任务里是「最不容易出错」的骨干。它的残差连接让梯度在反向传播时有一条高速公路直达浅层联邦学习场景下客户端本地训练轮次通常只有 2 到 5 轮如果骨干太深如 ResNet101本地数据量小的情况下浅层参数很难被充分更新聚合时浅层和深层的更新幅度会明显失衡。ResNet18 的问题是感受野偏小对分心驾驶里的细粒度动作比如手指夹烟、视线偏移这类微小姿态变化特征判别力不够。实际使用中我一般直接加载 torchvision 自带的 ResNet50 预训练权重把最后一层全连接改成需要的类别数。关键点在于预训练权重里的 BatchNorm 统计量在联邦聚合后会发生偏移这一步在第五章会专门讲。ResNet50 在联邦场景下的定位是主力模型跑出来的指标用来做其他两个模型的对照基线。2.2 VGG19 的定位特征可视化与固定算力设备部署VGG19 经常被诟病参数量大、计算冗余但它在分心驾驶检测里有一个不可替代的优势结构规整中间特征图可以直接用来做注意力可视化。实际部署时VGG19 适合烧录在算力相对固定的车机板上因为它的计算量是确定的不像 EfficientNet 那样按复合缩放系数浮动做算力规划更容易。另一个容易被忽略的点VGG19 全部使用 3×3 卷积感受野增长非常平缓这对小目标局部特征比如手部区域非常友好。如果你手里的分心驾驶数据集包含大量中远距离拍摄画面VGG19 的浅层特征会比 ResNet50 更容易保留手部纹理细节。代价是训练速度慢聚合时通信量也大——VGG19 的模型参数约 143M是 ResNet50 的两倍多如果你的联邦通信带宽有限这个开销要提前算清楚。2.3 EfficientNet 的复合缩放与异构客户端支持EfficientNet 出现在这个标题里承担的是「算力分层」的角色。EfficientNet-B0 的参数量只有 5.3M 左右但精度可以逼近 ResNet50B4 版本参数量约 19M精度反超 ResNet50。这意味着联邦学习里的不同客户端可以根据自身算力选择不同档位的 EfficientNet而聚合服务器依然可以用 ResNet50 做全局基准。模型参数量级输入尺寸联邦场景优势主要风险ResNet5025M224×224收敛稳定适合做基线训练速度中等VGG19143M224×224特征可解释性强结构规整通信开销大EfficientNet-B05.3M224×224算力友好的客户端首选精度上限受限EfficientNet-B419M224×224精度高且参数量适中输入尺寸需统一为 224 或 380EfficientNet 在联邦场景下要特别注意输入尺寸问题。原版 EfficientNet 的缩放策略里B4 以上版本建议输入 380×380但联邦学习要求所有客户端和服务器端的模型输入尺寸一致才能做参数聚合。我一般把所有模型统一到 224×224 输入B0 到 B2 在这个尺寸下精度损失很小B4 以上就不建议在联邦框架里用了损失太明显。2.4 联邦学习在分心驾驶任务上的特殊设定分心驾驶检测的联邦学习和经典 FedAvg 论文里的任务有一个显著差别类别极度不均衡。标准驾驶行为可能占 60% 以上打电话、喝水、化妆这些分心行为占比很小。如果按 IID 方式把数据随机切给客户端每个客户端拿到的分布基本一致FedAvg 还能正常工作但真实场景下不同司机有不同的驾驶习惯有的客户端可能 80% 数据都是正常驾驶有的客户端则集中了大量玩手机画面这就构成了典型的 Non-IID 分布。Non-IID 带来的直接后果是灾难性遗忘。客户端 A 本地训练后模型严重偏向玩手机类别聚合到全局模型后全局模型对正常驾驶类别的判别能力反而下降。这个问题在联邦学习领域有大量论文讨论但在工程实现里最常见的解法很简单控制本地训练轮次、调整客户端学习率、聚合时按样本量加权。具体参数在第四章给出。3. 数据切分与 Non-IID 模拟让联邦训练更接近真实场景3.1 分心驾驶数据集的类别体系与目录组织常见的分心驾驶数据集如 State Farm 分心驾驶数据集一般包含 10 个类别安全驾驶、发短信右手、发短信左手、打电话右手、打电话左手、操作收音机、喝水、化妆、整理头发、与乘客交谈。联邦学习场景下这些类别不会均匀出现在每个客户端上但工程实现的第一步依然是按标准分类目录组织数据。我一般把数据集放在如下结构里方便 torchvision 的ImageFolder直接读取data/ client_0/ train/ c0/ c1/ ... c9/ test/ c0/ ... client_1/ train/ ... server/ val/每个 client 目录代表一个联邦参与方c0到c9是类别目录图片直接放对应文件夹内。这种组织方式的好处是后续做数据加载、类别分布统计、Non-IID 模拟都非常直观。实际项目中不要把原始视频直接放进来分心驾驶检测通常先按帧抽图每 5 到 10 帧抽一张再按时间顺序切分成不同客户端的数据。3.2 用 Python 脚本模拟 Non-IID 分布真实场景的 Non-IID 数据很难直接获取工程上一般先用手头数据集做人工切分模拟出不同客户端的数据分布差异。下面这个脚本按狄利克雷分布给每个客户端分配不同类别的样本比例alpha参数控制分布偏移程度alpha越小分布越倾斜。import numpy as np from collections import defaultdict import shutil from pathlib import Path def simulate_non_iid_split( source_root: str, dest_root: str, num_clients: int 5, alpha: float 0.5, train_ratio: float 0.8, ): 将原始数据集按 Dirichlet 分布切分为 Non-IID 的联邦数据集。 alpha 越小客户端之间的类别分布差异越大alpha 越大越接近 IID。 src Path(source_root) dst Path(dest_root) classes sorted([p.name for p in src.iterdir() if p.is_dir()]) # 统计每个类别下的全部样本文件 class_files {} for cls in classes: class_files[cls] list((src / cls).glob(*.*)) # 为每个客户端生成一个 Dirichlet 采样比例决定每个类别的分配权重 client_class_probs np.random.dirichlet([alpha] * num_clients, sizelen(classes)) # 形状: [类别数, 客户端数]每行代表当前类别分给各客户端的比例 # 按比例分配文件 for client_id in range(num_clients): client_train_dir dst / fclient_{client_id} / train client_test_dir dst / fclient_{client_id} / test for cls in classes: files class_files[cls] probs client_class_probs[classes.index(cls)] # 打乱后按比例切分 np.random.shuffle(files) n_train int(len(files) * train_ratio) train_files files[:n_train] test_files files[n_train:] for subset, subset_files in [(train, train_files), (test, test_files)]: dest_dir client_train_dir / cls if subset train else client_test_dir / cls dest_dir.mkdir(parentsTrue, exist_okTrue) for f in subset_files: shutil.copy(str(f), str(dest_dir / f.name)) # 打印每个客户端的类别分布便于确认 Non-IID 程度 for client_id in range(num_clients): counts defaultdict(int) for cls in classes: n len(list((dst / fclient_{client_id} / train / cls).glob(*.*))) counts[cls] n total sum(counts.values()) print(fclient_{client_id} total{total}, dict(counts)) if __name__ __main__: simulate_non_iid_split( source_root./raw_data, dest_root./federated_data, num_clients5, alpha0.3, # 想更倾斜可以调到 0.1想接近 IID 调到 1.0 )这段代码的逻辑分成三步。第一步用np.random.dirichlet为每个类别生成一个概率向量向量长度等于客户端数量表示该类别在不同客户端的比例分配第二步按这个比例把每个类别的文件划分到各客户端目录第三步在客户端内部再按train_ratio切出训练集和测试集保证每个客户端都有自己的本地测试数据。参数设置上有几个经验值。alpha取 0.3 到 0.5 时客户端之间的类别分布差异最接近真实驾驶场景因为真实场景下大多数人以正常驾驶为主少数客户端的异常行为数据高度集中。num_clients建议先设 3 到 5 个客户端太少体现不出联邦学习的优势太多则单机训练时间会拖长不利于排查问题。train_ratio设 0.8 比较稳妥因为联邦学习的本地测试集不仅用来评估精度还要用来观察每个客户端对全局模型的贡献。3.3 客户端数据增强策略与 IID 切分的对照实验做联邦学习实验时我强烈建议同时准备一份 IID 切分的数据做对照。IID 切分其实非常容易实现只需要把alpha调到 100 以上狄利克雷分布就会趋近均匀每个客户端拿到的类别比例基本一致。两个实验跑出来之后你能明显看到 Non-IID 条件下的全局模型精度会低 3 到 8 个百分点这能帮你理解联邦学习在真实场景下遇到的瓶颈到底是什么。数据增强策略上本地端训练时使用随机水平翻转、随机旋转 10 度、随机色彩抖动就足够了。联邦场景下不要使用过于激进的数据增强因为客户端本地数据量小增强过度相当于人为改变了数据分布聚合后反而破坏全局模型的稳定性。我见过一个翻车案例是把 CutOut 和 MixUp 同时用在客户端本地训练上结果全局模型的精度比单客户端还低这就是本地增强过度导致的特征偏移。4. 核心代码架构从数据加载到 FedAvg 聚合的完整链路4.1 项目文件结构与模块职责一个能跑通的联邦学习分心驾驶检测工程通常包含以下模块每个模块职责单一方便调试和替换fed_driver/ models.py # 三个骨干网络的定义与加载逻辑 client.py # 客户端本地训练逻辑 server.py # 服务器端参数聚合逻辑 utils.py # 数据加载、评测、日志工具 main.py # 主训练流程控制 config.yaml # 全局超参数配置models.py负责根据配置创建指定骨干网络client.py封装单客户端本地训练server.py实现 FedAvg 参数加权聚合utils.py里放数据加载和精度评估main.py串联整个流程。这个结构最大的好处是想换聚合算法比如 FedProx、FedNova只需要改server.py想换骨干网络只改models.py的配置文件。4.2 模型定义代码统一接口方便切换骨干网络# models.py import torch import torch.nn as nn from torchvision import models def build_model(name: str, num_classes: int 10, pretrained: bool True): 按名称创建骨干网络统一输出分类头。 支持: resnet50, vgg19, efficientnet_b0 if name resnet50: model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1 if pretrained else None) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) elif name vgg19: model models.vgg19(weightsmodels.VGG19_Weights.IMAGENET1K_V1 if pretrained else None) in_features model.classifier[6].in_features model.classifier[6] nn.Linear(in_features, num_classes) elif name efficientnet_b0: model models.efficientnet_b0( weightsmodels.EfficientNet_B0_Weights.IMAGENET1K_V1 if pretrained else None ) in_features model.classifier[1].in_features model.classifier[1] nn.Linear(in_features, num_classes) else: raise ValueError(fUnsupported model: {name}) return model这段代码的关键在于最后替换分类头的逻辑。三个模型的分类头位置不同ResNet50 的fc是直接挂在最后的全连接层VGG19 的分类头是一个多层classifier模块需要替换第 7 层EfficientNet 的classifier[1]是 Dropout 后的全连接层。如果你直接照搬某个模型的替换方式去替换另一个会报维度不匹配或根本没替换成功。我见过有人把 ResNet50 的替换方式直接套到 EfficientNet 上结果model.classifier的拼接维度全错模型跑起来精度只有 10% 出头这就是分类头替换位置搞错了。4.3 客户端本地训练实现# client.py import torch from torch.utils.data import DataLoader class FedClient: def __init__(self, client_id, model, train_loader, test_loader, device): self.client_id client_id self.model model self.train_loader train_loader self.test_loader test_loader self.device device def local_train(self, epochs2, lr1e-4, momentum0.9, weight_decay1e-5): 在客户端本地数据上训练返回更新后的模型状态字典。 注意这里不返回训练数据本身只返回模型参数这是联邦学习的核心约束。 model self.model.to(self.device) model.train() criterion torch.nn.CrossEntropyLoss() optimizer torch.optim.SGD( model.parameters(), lrlr, momentummomentum, weight_decayweight_decay ) for epoch in range(epochs): total_loss 0.0 correct 0 total 0 for inputs, targets in self.train_loader: inputs, targets inputs.to(self.device), targets.to(self.device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, targets) loss.backward() optimizer.step() total_loss loss.item() * inputs.size(0) _, predicted outputs.max(1) total targets.size(0) correct predicted.eq(targets).sum().item() avg_loss total_loss / total acc 100.0 * correct / total print(f[client {self.client_id}] epoch {epoch1}/{epochs} floss{avg_loss:.4f} acc{acc:.2f}%) return model.state_dict()本地训练的核心逻辑和普通 PyTorch 训练没本质区别但有几个联邦专属的设定值得注意。epochs参数在联邦场景下不能设太大我一般设 2 到 3 轮。因为联邦学习是「服务器聚合 → 分发 → 本地训练 → 回传」的循环本地训练轮次过大模型在局部数据上过拟合回传的权重偏移严重聚合后全局模型反而退化这就是经典的客户端漂移问题。lr设置上联邦场景下一律建议比单机训练小单机训 ResNet50 可以用 1e-3联邦本地训练我会降一个量级到 1e-4避免本地快速过拟合。优化器选择上联邦学习论文里常见 SGD不建议本地用 Adam原因后面避坑部分会说。4.4 服务器端 FedAvg 聚合实现# server.py import copy import torch def fed_avg(global_model, client_states, client_weights): 按客户端样本量比例加权聚合模型参数。 client_states: list of state_dict每个元素来自一个客户端 client_weights: list of float每个客户端的样本占比总和应为 1 global_state global_model.state_dict() # 先做除法初始化避免后续循环里重复拷贝状态字典 for key in global_state.keys(): global_state[key] torch.zeros_like(global_state[key]) for state, weight in zip(client_states, client_weights): for key in global_state.keys(): global_state[key] state[key] * weight global_model.load_state_dict(global_state) return global_modelFedAvg 的实现就这么几行但有一个隐性的坑BN 层的num_batches_tracked参数会被错误地做加权平均。num_batches_tracked是 BatchNorm 里记录训练步数的标量它不应该被聚合否则全局模型的 BN 状态会和真实统计量脱节。我一般会额外加一个过滤逻辑把名称中包含num_batches_tracked的键直接跳过或者在聚合前把每个客户端的该参数重置为 0。下面的写法更稳妥。def fed_avg_with_bn_fix(global_model, client_states, client_weights): FedAvg 聚合跳过 BN 的 num_batches_tracked 计数器。 global_state global_model.state_dict() skip_keys [k for k in global_state.keys() if num_batches_tracked in k] for key in global_state.keys(): global_state[key] torch.zeros_like(global_state[key]) for state, weight in zip(client_states, client_weights): for key in global_state.keys(): if key in skip_keys: continue global_state[key] state[key] * weight global_model.load_state_dict(global_state) return global_modelclient_weights的计算方式是每个客户端本地样本数除以全体客户端样本数之和。这一点务必在客户端上报模型参数时一并上报样本数量不要用本地训练的 batch 数量替代因为最后一批往往不足一个 batch样本数统计会偏少。4.5 主训练循环与全局评测# main.py import torch from models import build_model from client import FedClient from server import fed_avg_with_bn_fix def run_federated_learning( model_name, clients_data, num_clients, num_rounds, local_epochs, lr, device ): clients_data: list of (train_loader, test_loader, num_samples) global_model build_model(model_name, num_classes10).to(device) total_samples sum([c[2] for c in clients_data]) client_weights [c[2] / total_samples for c in clients_data] for round_idx in range(num_rounds): print(f--- round {round_idx 1}/{num_rounds} ---) client_states [] # 每个客户端基于全局模型参数做本地训练 for client_id, (train_loader, test_loader, num_samples) in enumerate(clients_data): client_model build_model(model_name, num_classes10).to(device) client_model.load_state_dict(global_model.state_dict()) fed_client FedClient( client_idclient_id, modelclient_model, train_loadertrain_loader, test_loadertest_loader, devicedevice, ) state fed_client.local_train(epochslocal_epochs, lrlr) client_states.append(state) # 聚合客户端参数更新全局模型 global_model fed_avg_with_bn_fix(global_model, client_states, client_weights) # 在服务端验证集上评估全局模型 val_acc evaluate(global_model, server_val_loader, device) print(fround {round_idx 1} global val acc{val_acc:.2f}%)主循环的结构清楚每一轮先复制全局模型参数分发给客户端客户端在本地数据上训练几个 epoch返回模型状态字典服务器端加权聚合成新的全局模型然后在服务端保留的验证集上做评估。num_rounds我一般设 30 到 50 轮注意这和本地epochs是两个不同维度的超参数——本地 epochs 控制单次训练的过拟合风险全局 rounds 控制联邦收敛的进度。评估函数这里不展开它就是标准的model.eval()torch.no_grad()循环但有一个必须注意的点服务端验证集必须独立于所有客户端数据不能从客户端测试集里抽样拼接否则评估结果虚高你无法判断联邦学习是否真的学到了泛化特征。5. 避坑分心驾驶联邦训练中的五个翻车点5.1 BatchNorm 统计量在聚合后严重偏移现象全局模型聚合后的验证精度远低于所有客户端本地模型精度的平均值有时甚至会跌到 10% 以下接近随机猜测。原因每个客户端本地训练时BatchNorm 层会重新计算均值和方差统计量不同客户端数据分布不同统计量就不同。FedAvg 对 BN 层参数做简单加权平均后全局模型的 BN 统计量和真实分布完全对不上推理时归一化结果混乱。解决方案一是聚合时跳过 BN 层的running_mean、running_var和num_batches_tracked不聚合这些参数只在每轮聚合后让全局模型跑一遍服务端的小批量数据重新校准 BN 统计量。方案二是直接把模型的 BN 层替换成 GroupNorm彻底避开统计量跨客户端漂移的问题。我一般先用方案一改动最小如果精度还是不稳定再换 GroupNorm。5.2 本地 epoch 过大导致灾难性遗忘现象联邦训练过程中全局模型精度先升后降尤其到 15 轮之后开始明显波动客户端本地模型在各自测试集上精度很高但全局评测崩了。原因本地训练轮次过多客户端模型在自己的小数据集上严重过拟合回传给服务器的权重已经「忘记」了全局特征。多客户端一起回传过拟合权重聚合后全局模型被拉向一个奇怪的平衡点精度自然下降。解决把本地epochs压到 2最多不超过 3。同时把本地学习率降到 1e-4 到 5e-4 区间。我见过一个工程做法是本地训练时用早停如果 loss 在 10 个 batch 内不再下降就提前终止效果也不错但要注意早停逻辑不能让不同客户端训练步数差异过大否则聚合权重失衡。5.3 客户端数据量极不均衡导致小样本客户端权重被吞没现象某个客户端只有几十张图另一个客户端有几千张图聚合后全局模型明显偏向大数据客户端小数据客户端的本地精度跌到极低。原因FedAvg 按样本量比例加权样本量差异过大时小客户端梯度贡献被稀释。这在分心驾驶场景很常见——有的车辆使用频率低采集到的分心视频样本天然少。解决先做样本量下限过滤客户端本地样本少于 200 张的不参与本轮训练避免无效聚合。另外可以采用一种简单的 log 加权替代线性加权weight log(1 num_samples) / sum(log(1 num_samples))适度抬升小客户端的权重。这个改动在 FedAvg 框架下只改一行代码能明显改善小客户端的参与感。5.4 三个模型直接不能互换聚合现象想验证模型异构联邦让一部分客户端跑 ResNet50另一部分跑 VGG19然后直接在服务器端做参数平均结果报出维度不匹配错误或者强行对齐后精度崩溃。原因不同骨干网络的层结构、参数数量完全不同ResNet50 的卷积层参数不能和 VGG19 的卷积层参数做加权平均。FedAvg 的前提是所有客户端使用相同的模型结构。解决异构联邦需要专门的算法支持不是简单 FedAvg 能解决的。常见做法是用知识蒸馏而不是参数平均服务器端维护一个教师模型收集各客户端的预测 logits 来更新教师模型。如果你就是想在同一个工程里对比三个模型做法是跑三组独立实验每组所有客户端统一用同一个骨干。同样为了对比客户端算力不一时可以按客户端算力分组算力强的组跑 EfficientNet-B2算力弱的组跑 B0两组各自做同构联邦最后在服务端比较精度。5.5 本地优化器使用 Adam 导致聚合发散现象本地训练画面很漂亮loss 快速下降但每轮聚合后全局精度提升极小甚至来回震荡。原因Adam 的动量项和一阶二阶矩估计在客户端本地保留着历史梯度信息这些状态不参与联邦聚合。每轮本地训练初始化时模型的参数从服务器加载但 Adam 的动量状态在每个客户端上独立累积导致本地更新方向严重依赖于客户端自己的历史轨迹聚合后全局模型相当于被多个不同轨迹的模型平均收敛自然困难。解决本地训练统一使用 SGD momentummomentum 设 0.9 左右。Adam 只适合用在单机集中式训练联邦场景下它的自适应学习率机制反而成了拖累。如果你想在联邦里用自适应优化器可以在服务器端对聚合后的全局梯度做 Adam 更新而不是在客户端用。6. 用混淆矩阵和特征可视化验证联邦模型再做算力分层联邦学习工程里有一个容易犯的错误只盯着全局验证集精度的数值看却不知道模型到底学到了什么、哪里在混淆。分心驾驶检测的类别之间有大量语义重叠发短信右手和打电话右手在画面上的差异可能只是手里有没有贴着耳朵这种细微差别的结果靠一个总精度数字完全看不出来。我的习惯是每 5 轮做一次全局模型在服务端验证集上的混淆矩阵并同时记录客户端分组统计。具体做法是服务端单独保留一个按类别分层抽样的验证集每 5 轮输出一个 10×10 的混淆矩阵重点关注两类错误——安全驾驶被误报为分心以及不同分心类别之间的互相混淆。前者直接决定系统的误报率后者决定系统能不能真正区分危险行为。我实际跑下来的经验是ResNet50 在安全驾驶和打电话的区分上表现最好VGG19 在发短信和整理头发这类手部细节相近的类别上略强这两个结论可以作为模型选型的参考。特征可视化方面VGG19 的优势在这里体现得最明显。提取 VGG19 最后一层卷积的特征图使用 Grad-CAM 对测试图片生成热力图可以直观看到模型到底是根据方向盘附近的手部区域还是根据驾驶员脸部区域做判断。如果热力图集中在背景上基本可以断定模型学到了数据集的背景偏差比如某个客户端采集环境里有特殊光照模型学到的是光照特征而不是驾驶行为特征这时候需要回到数据层面做色彩增强或者更换骨干网络。最后一个进阶技巧是客户端算力分层。分心驾驶检测的客户端不可能都是同一种设备我的做法是建立一个算力登记表每个客户端上报自己的推理耗时和显存占用服务器统一调度分组算力强且带宽高的客户端跑 ResNet50 或 EfficientNet-B2算力弱的跑 EfficientNet-B0分组内部同构、组间异构最后在服务端合并评估。这种分层策略比强行让所有客户端跑同一个模型更贴近真实部署也更容易让客户愿意参与联邦训练——毕竟没有谁愿意为了训练一个全局模型把自己设备的性能拖垮。这套方案做下来我最大的收获不是模型精度从 87% 提到 91%而是搞清楚了在隐私约束下训练视觉模型的节奏本地训练宁少勿多聚合参数按样本量加权BN 统计量不做特殊处理就一定会翻车。如果你第一次跑联邦学习建议先用 ResNet50 加 5 个客户端以 alpha 0.3 的 Non-IID 切分跑通整条链路再逐步加入 VGG19 和 EfficientNet 的对比实验。框架可以先不用就手写 FedAvg 几十行代码跑通了再考虑换 Flower 这类联邦框架也不迟。希望帮到你。本文还有配套的精品资源点击获取
返回列表