简介:基于FedAvg联邦平均算法与SMOTE过采样优化的Python项目,面向联邦学习场景下的信用卡欺诈交易检测,适合计算机、人工智能等相关专业学生作为毕设或课程设计参考。项目通过Server与Clients对象模拟服务器与节点间的双向参数传递,在保护数据隐私的前提下解决银行间数据不可共享的痛点,同时利用SMOTE过采样缓解欺诈样本不平衡问题,整体思路完整、可直接运行。压缩包共8个文件,以Python源码为主,辅以说明文档、流程图与CSV数据集,整体大小约43MB,代码结构清晰且经过测试。目前已有173人学习下载,适合需要完整可运行方案、论文级项目演示或远程指导的初学者。下载后可直接对照说明文档及模型、客户端、服务端等模块进行二次改造,快速理解联邦训练全流程。
1. 联邦信用卡欺诈检测为什么要把SMOTE塞进FedAvg
信用卡欺诈数据是机器学习里最典型的一类"脏数据":正负样本比例动辄 1:500 甚至更低,单家银行的数据规模又不足以训练出可靠模型。把 FedAvg(联邦平均)和 SMOTE(少数类过采样)组合起来,是在隐私约束下解决这个问题的常见方案——客户端在本地完成少数类合成,服务器只聚合模型权重,既不让交易明细离开本地,又能缓解类别不平衡对梯度更新的扭曲。
下面直接讲清楚 SMOTE 该放在联邦流程的哪一步、参数怎么设、评估看什么指标,以及跑通这套管线会踩到的几个坑,适合已经在做联邦学习或信用卡欺诈交易检测的工程师。
2. FedAvg训练循环拆解:本地更新与服务器聚合的边界
2.1 三步协议:采样客户端、本地训练、按样本数加权聚合
FedAvg 是联邦学习里绕不开的基线算法,协议本身只有三步。服务器把当前全局模型权重分发给本轮参与的一部分客户端;每个客户端在本地数据上独立训练若干轮梯度下降;客户端把训练后的权重传回服务器,服务器按公式 w_global = Σ(n_k / n) · w_k 做加权平均,权重 n_k / n 是客户端 k 的本地样本数占本轮总样本的比例,然后进入下一轮。整个循环重复若干轮,直到全局模型在验证集上的指标不再提升。
这里最关键的边界是"数据不出客户端"。集中式训练要求把所有交易流水收集到同一台机器,FedAvg 只交换模型参数,服务器接触不到任何一条交易明细,这对信用卡数据这种强隐私场景几乎是硬性要求。另一个容易被忽略的细节是:FedAvg 聚合的是权重而不是梯度,所以服务器不需要关心客户端用什么优化器、学习率怎么调,兼容性更好;代价是每轮都要上传完整模型权重,通信开销大。工程上常见做法是对上传的更新做 Top-k 稀疏化,也就是偏置压缩,只传输权重变化量中显著的部分,把每轮通信量压到一个很低的水平。
2.2 两个关键超参数:本地训练轮数E与客户端采样比例C
FedAvg 和分布式SGD的关键差异在通信频率:分布式SGD 每个 batch 同步一次,FedAvg 让客户端本地连续训练 E 个 epoch 才同步一次。E 越大,通信轮次越少,但客户端之间权重发散越严重,聚合出来的全局模型可能出现漂移;E = 1 时最接近普通SGD 的收敛轨迹,但通信压力最大。对信用卡交易这种特征维度不高、单客户端样本量较大的场景,E 取 1 到 5 是合理区间。
客户端采样比例 C 决定每轮有多少比例的客户端参与聚合。客户端数量多但每轮只抽一小部分时,数据量小的机构容易被长期跳过,全局模型会偏向数据量大的那端。C 本身不改变通信内容,但会显著影响聚合方差。下面的组合是常用起点:
| 场景 | E 推荐 | C 推荐 | 说明 |
|---|---|---|---|
| 客户端少于10个 | 3~5 | 1.0 | 全部参与,聚合方差最小 |
| 客户端50个以上 | 1~2 | 0.2~0.3 | 控制通信开销 |
| Non-IID严重 | 1 | 0.5 | 缩短本地训练减少漂移 |
如果 SMOTE 已经让各客户端的分布差异进一步拉大,C 取大值比取小值更安全,因为每轮有更多客户端参与,意味着合成样本的特征能被聚合更充分地吸收,而不是集中在少数几轮的权重更新里。
2.3 一个最小可跑的FedAvg Python骨架
FedAvg 的实现不复杂,难点在工程化。下面用 PyTorch 写一个最小骨架,模型定义和数据加载省略,只保留联邦循环本身:
import random import copy def fedavg_round(global_model, clients, client_fraction=0.3, local_epochs=3, lr=0.01): # 1. 服务器按比例随机采样本轮参与的客户端 sampled = random.sample(clients, max(1, int(len(clients) * client_fraction))) global_weights = copy.deepcopy(global_model.state_dict()) local_weights, local_sizes = [], [] for client in sampled: # 2. 每个客户端从全局权重出发,在本地数据上做若干轮梯度下降 client.model.load_state_dict(global_weights) client.train_on_local_data(local_epochs=local_epochs, lr=lr) local_weights.append(copy.deepcopy(client.model.state_dict())) local_sizes.append(client.num_samples) # 3. 按各客户端样本数占比加权平均,得到新的全局模型 total = sum(local_sizes) new_weights = {} for key in global_weights: weighted = [w[key] * (n / total) for w, n in zip(local_weights, local_sizes)] new_weights[key] = sum(weighted) global_model.load_state_dict(new_weights) return global_model逻辑上这四步对应协议的三步:采样、本地训练、加权聚合。两个细节值得注意:一是 load_state_dict 之前必须深拷贝全局权重,否则下一个客户端会读到上一个客户端的更新结果;二是服务器只通过 state_dict() 拿权重,如果模型带 BatchNorm,统计信息也要一并传输,否则推理时分布会对不上。参数层面,client_fraction 控制每轮参与方,影响通信量和聚合方差;local_epochs 控制本地推进深度,直接决定模型漂移程度;lr 在联邦场景下要比单机训练保守,0.01 到 0.05 起步比较稳,因为加权聚合本身会引入额外的梯度方差。
提示:生产环境会在聚合链路上加安全聚合或差分隐私噪声,协议主链路不变,但私有化部署的起点不是加密,而是先把上面的骨架跑通。
3. SMOTE过采样放对位置:客户端本地合成而非全局合成
3.1 信用卡欺诈的极端不平衡与SMOTE线性插值原理
信用卡欺诈交易检测是类别不平衡的典型现场。公开的信用卡欺诈数据集中 28 万余笔交易只有 492 笔欺诈,正负比接近 1:578。在这种分布下,一个把所有样本都判为正常的分类器准确率高达 99.8%,但这个模型一点用都没有。欺诈检测真正关心的是少数类召回率,而常规梯度下降会被多数类主导,模型倾向学会"全部判正常"这个偷懒解。
SMOTE(Synthetic Minority Over-sampling Technique)解决的是"少数类样本不够模型学"的问题。它不是复制已有样本,而是对每个少数类样本 x,用 K 近邻(默认欧氏距离)找到 K 个邻近少数类样本,随机挑一个 x_i,在特征空间上做线性插值生成新样本:x_new = x + λ · (x_i - x),λ 是 0 到 1 的随机数。这样生成的新样本落在两个真实样本的连线上,相当于在少数类分布内部做插值,比简单复制多了多样性,能有效缓解过采样附带的过拟合。
在联邦场景里,SMOTE 的位置只有一种合理选择:放进各个客户端的本地处理链路。服务器拿到的只有模型权重,没有原始特征,无法在全局层面做合成;而客户端在本地拥有完整交易特征,天然具备做插值的基础。这套"本地过采样、全局聚合权重"的流程,本质上是先用 SMOTE 修正每个客户端的本地分布,再让 FedAvg 去平均修正后的学习结果。
3.2 用imbalanced-learn实现客户端内SMOTE:sampling_strategy与k_neighbors
实现上直接用 imbalanced-learn 的 SMOTE 类就行,不需要自己写近邻搜索。每个客户端在本地训练前,对训练集单独执行 fit_resample:
from imblearn.over_sampling import SMOTE from sklearn.model_selection import train_test_split def local_oversample(X, y, sampling_strategy=0.3, k_neighbors=5): # SMOTE 只作用在训练集上,验证集保持原始分布用于真实评估 X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42) smote = SMOTE(sampling_strategy=sampling_strategy, k_neighbors=k_neighbors, random_state=42) X_res, y_res = smote.fit_resample(X_train, y_train) print(f"训练集: {X_train.shape[0]} -> {X_res.shape[0]}, " f"正样本: {y_train.sum()} -> {y_res.sum()}") return X_res, y_res, X_val, y_val两个参数决定了合成强度:sampling_strategy 表示合成后少数类占多数类的目标比例,0.3 意味着把欺诈样本补到正常样本量的三成,这是一个偏保守的起步值;调大它会让训练集里合成样本占比上升,模型对插值区域的拟合更强,但也更容易记住合成噪声。k_neighbors 控制每个少数类样本合成时参考的近邻数,默认 5,如果某个客户端正样本数量很少、类别重叠度高,可以降到 3,减少混入边界样本的风险。注意 train_test_split 的 stratify=y 必须保留,否则切分后某一端的正样本比例漂移,后续评估会和训练分布错位。
提示:fit_resample 只允许作用在训练集上。验证集、测试集一旦被过采样,评估出来的 PR 指标会明显虚高,上线后召回率会断崖下跌,这是这套管线里最容易踩的坑。
3.3 合成比例不能全局统一:两个Non-IID层面的原因
很多实现把 sampling_strategy 设成全局常量,这在实际联邦数据上有两个问题。第一个是少数类绝对数量差异。假设机构 A 一个月只有 200 笔欺诈样本,机构 B 有 5000 笔,用同一个 0.3,A 合成出的样本只有几百条,模型学不到可用的欺诈模式;B 则合成上千条,插值噪声被成倍放大。合成量应该跟着本地正样本数走,而不是跟着全局统一比例走。
第二个是标签分布偏移(label distribution skew)。不同机构的欺诈率天然不同,有的侧重线上盗刷、有的侧重线下伪卡,合成样本会放大这种本地偏置。如果全局用同样的 SMOTE 比例,聚合后的全局模型会被少数几个合成样本多的客户端带偏。我一般让客户端按本地正样本占比动态计算 sampling_strategy,在 0.2 到 0.5 之间取,而不是全局写死一个值。
4. FedAvg+SMOTE的联邦信用卡欺诈交易检测管线与关键参数
4.1 端到端流程:横向切分、本地过采样、循环聚合
整套管线按横向联邦设计,每个客户端拥有相同的特征列,各自持有不同的交易样本。先按机构维度把原始数据切成 N 份模拟客户端,再用 Dirichlet 分布控制各客户端的正样本占比,制造 Non-IID 效果,这样训练出的结果更接近真实部署环境,而不是"数据均匀拆分"这个理想假设。整体流程如下:
import numpy as np def run_fed_smote(global_model, clients, rounds=50, eval_interval=10): # 每个客户端按本地正样本占比配置SMOTE比例,不全局统一 for c in clients: c.sampling_strategy = float(np.clip(c.positive_ratio * 3, 0.2, 0.5)) c.apply_smote() # 在本地训练集上执行 fit_resample for rnd in range(rounds): # 聚合时传低学习率,避免SMOTE合成样本放大梯度震荡 fedavg_round(global_model, clients, client_fraction=0.4, local_epochs=2, lr=0.01) # 每eval_interval轮用各客户端原始验证集评估一次 if rnd % eval_interval == 0: aps = [average_precision_score(c.y_val, c.predict(global_model)) for c in clients] print(f"round {rnd}: mean AP = {np.mean(aps):.4f}") return global_model流程顺序是固定的:先配 SMOTE、再训练、最后评估。apply_smote 在每轮 FedAvg 开始前一次性做好,不需要每轮重复 fit,因为合成样本的随机种子固定后,重复执行只会浪费计算。评估时用的是客户端原始验证集,没有经过过采样,这样得到的 AP 才是全局模型对真实分布的泛化能力。lr 在联邦场景下调低到 0.01,是因为 SMOTE 合成样本放大了本地梯度方向的分歧,学习率太高会让聚合后的权重更新震荡。
4.2 五个必调参数与推荐区间
| 参数 | 推荐区间 | 作用 | 调大的后果 |
|---|---|---|---|
| local_epochs (E) | 1~5 | 本地连续训练的轮数 | 客户端权重漂移加重 |
| client_fraction (C) | 0.2~1.0 | 每轮参与的客户端比例 | 聚合方差变小但通信变大 |
| sampling_strategy | 0.2~0.5 | SMOTE合成后少数类占比目标 | 过拟合合成插值区域 |
| k_neighbors | 3~7 | SMOTE合成时的近邻数 | 近邻中混入多数类样本 |
| batch_size | 64~256 | 本地训练批大小 | 梯度噪声增大 |
这里单独说明 sampling_strategy 和 k_neighbors 的联动:当合成比例调高时,近邻池里少数类样本密度变大,k_neighbors 可以适当调大,让插值更平滑;反之合成比例低时 k_neighbors 保持默认 5 即可。batch_size 在联邦场景容易被忽略,其实它直接决定本地梯度估计的方差,而 FedAvg 聚合本身就带方差,两者叠加会让收敛变慢。
4.3 评估指标选Precision-Recall而非Accuracy
欺诈检测场景下 Accuracy 是近乎必然的错误选择,0.17% 的正样本占比让准确率失去了区分能力。PR 曲线的横纵轴分别是召回率和精确率,它直接回答"我抓到的欺诈里有多少是真的、真实欺诈里有百分之多少被抓住了"这两个业务问题。用 scikit-learn 实现:
from sklearn.metrics import precision_recall_curve, average_precision_score # y_true: 原始测试集标签;y_score: 模型输出的欺诈概率,两者都未过采样 precision, recall, thresholds = precision_recall_curve(y_true, y_score) ap = average_precision_score(y_true, y_score) # 部署阈值选择:在精确率不低于0.8的前提下,尽量提高召回率 deploy_threshold = 0.5 candidates = [(p, r, t) for p, r, t in zip(precision, recall, thresholds) if p >= 0.8] if candidates: deploy_threshold = max(candidates, key=lambda x: x[1])[2] # 取召回率最大的阈值 print(f"AP = {ap:.4f}, deploy_threshold = {deploy_threshold:.4f}")联邦环境下的评估要落在每个客户端上:全局模型下发后,各客户端在本地原始测试集上计算 AP,服务器再按样本数加权平均得到联邦 AP。这个值比 ROC-AUC 更能反映少数类学习效果,因为 PR 曲线对正样本比例的敏感度更高,欺诈率低的客户端贡献会被正确压低。
5. 解决联邦SMOTE的收敛慢与局部过拟合:三个调优技巧
5.1 按正样本占比加权聚合,替代纯样本数加权
FedAvg 默认权重 n_k / n 在数据均衡时没问题,SMOTE 之后各客户端的有效正样本差异被放大了。简单有效的改法是让客户端在上传权重时附带一个合成样本计数,聚合权重改为 w_k = n_k + α · n_syn_k,α 取 0.3 到 0.5。这样合成样本多的客户端在聚合中占比略高,能补偿本地过采样带来的方差。α 不要超过 0.5,否则少数几个客户端会把合成噪声放大到全局,表现就是联邦验证集 AP 在小幅上升后迅速掉头。
5.2 限制合成倍数,必要时退回类权重损失
SMOTE 不是越猛越好。某个客户端正样本不到 200 条时,把 sampling_strategy 调到 0.5 也合成不出可用的分布,反而训练时间翻倍。我一般限制每个客户端合成样本不超过原始正样本的 3 倍,超过就降比例。如果限制了合成量,验证集 AP 仍然不涨,直接删掉 SMOTE,改用加权交叉熵损失。SMOTE 和类权重叠加往往不如二者选其一,因为两者都在放大少数类梯度,叠加后容易让模型在合成样本上过拟合,验证集 AP 反而低于只做其中一项。
5.3 用客户端原始验证集的AUC-PR做早停
合成样本不参与验证,所以原始验证集上的 AP 是判断 SMOTE 是否真正有效的唯一信号。每 5 轮在所有客户端原始验证集上算一次平均 AP,连续 10 轮不增长就停止训练并回滚到最佳轮次的权重。如果 AP 在某一轮突然掉点,先检查两件事:验证集是否被无意中过采样,以及 local_epochs 是否因为 SMOTE 拉大了客户端权重差异。这两个问题比超参数调优更常见。
本文还有配套的精品资源,点击获取