
简介这份资源面向计算机、人工智能等专业的在校学生与联邦学习入门者提供一套基于FedAvg联邦学习算法与SMOTE过采样优化的信用卡欺诈交易检测完整项目源码。项目通过构建Server与Clients对象模拟真实场景下服务器与节点间的双向参数传递在保护各银行数据隐私、避免数据集共享的前提下提升欺诈识别效果适合作为毕设、课程设计或算法进阶练习。压缩包共8个文件约43.14MB包含5个Python源码文件、1个说明文档、1张流程示意图和1份信用卡交易数据集分别对应模型定义、客户端与服务端逻辑、数据处理及运行入口结构清晰便于按模块阅读。目前已有173人学习。代码均经测试运行成功答辩评审平均分达96分读者可据此掌握联邦学习与不平衡数据处理的完整实现思路并在此基础上修改扩展功能。1. 联邦信用卡欺诈检测里FedAvg 加 SMOTE 到底解决了什么信用卡欺诈检测是典型的小样本极端不平衡二分类任务正常交易占比常年 99.5% 以上欺诈样本可能只有千分之几。更麻烦的是真实业务里交易数据分散在各家银行、各个支付机构手里谁都不愿意把原始流水交出去——这既是合规红线也是商业机密。联邦学习Federated Learning就是冲着这个矛盾来的数据不动模型动。而 FedAvgFederated Averaging是目前最主流、最容易复现的联邦聚合算法没有之一。但把 FedAvg 直接套到欺诈检测上会撞上两堵墙。第一堵墙是本地数据本身就不平衡每个客户端手里的欺诈样本都少得可怜本地训练几轮模型就退化成「全预测为正常」的摆烂状态。第二堵墙是聚合之后全局模型被多数正常样本主导少数类的梯度信号在加权平均里被稀释掉。SMOTESynthetic Minority Over-sampling Technique过采样就是用来在本地把少数类「造」出来的让每个客户端在训练前先补足欺诈样本再参与 FedAvg 聚合。这套组合适合谁适合手里有分散数据、又必须做欺诈识别的团队——银行风控、第三方支付、消费金融以及做联邦学习课程设计或高分项目的同学。它不解决「数据能不能共享」的合规问题但能在不共享原始数据的前提下把不平衡检测的召回率往上抬一截。下面我按「原理选型 → 本地跑通 → 参数调优 → 踩坑排查 → 进阶验证」的顺序把这条链路讲透。2. FedAvg 与 SMOTE 的耦合逻辑为什么不能简单叠加2.1 FedAvg 的聚合公式与欺诈场景的错位FedAvg 的核心就一行公式全局模型参数等于各客户端参数按样本量加权平均。标准写法是 w_global Σ(n_k / n) · w_k其中 n_k 是第 k 个客户端的样本数n 是总样本数。这个加权逻辑在图像分类里没问题因为各客户端类别分布大致均衡。但放到欺诈检测里n_k 里 99% 以上是正常交易加权权重几乎完全由正常样本决定少数类的更新方向被淹没。更隐蔽的问题是客户端漂移client drift。每个客户端的欺诈模式不一样——有的盗刷集中在境外大额有的集中在凌晨小额试探。FedAvg 把这些异构的本地模型强行平均得到的全局模型在任何一个客户端上都不是最优。这就是联邦学习里常说的「非独立同分布Non-IID」难题欺诈场景是 Non-IID 的极端形态。所以正确的做法不是「先 FedAvg 再 SMOTE」而是「先 SMOTE 再本地训练再 FedAvg」。顺序反了过采样就白做了。2.2 SMOTE 在本地客户端里的插入位置SMOTE 的原理是对每个少数类样本找它的 k 个最近邻少数类样本在两者连线上随机插值生成新样本。公式是 x_new x_i λ · (x_nn - x_i)λ 是 [0,1] 的随机数。它比简单复制样本好在能扩展少数类的决策边界而不是让模型死记硬背几个固定点。在联邦场景里SMOTE 必须放在本地训练之前、数据划分之后。关键约束是只能在训练集上做 SMOTE验证集和测试集绝对不能碰。我见过太多人为了「让指标好看」把 SMOTE 应用到全量数据再划分结果测试集里混入了合成样本指标虚高十几个点上线直接翻车。这是血泪经验别踩。还有一个细节SMOTE 的 k 近邻参数 k_neighbors 不能大于本地少数类样本数减一。如果某个客户端只有 3 个欺诈样本k_neighbors 设 5 会直接报错。所以要么设一个自适应的 k要么在客户端样本太少时退化成随机过采样。2.3 一个可复现的本地 SMOTE 实现下面这段代码是单个客户端的本地过采样逻辑用 imbalanced-learn 库实现。注意它只处理训练集并且对少数类样本过少的客户端做了降级保护。import numpy as np from imblearn.over_sampling import SMOTE from collections import Counter def local_smote(X_train, y_train, target_ratio0.3, min_samples6): 本地客户端 SMOTE 过采样 target_ratio: 少数类占多数类的目标比例 min_samples: 少数类样本数低于此值时降级为随机过采样 counter Counter(y_train) minority_count counter[1] majority_count counter[0] # 少数类太少SMOTE 无法找近邻降级处理 if minority_count min_samples: # 随机复制到目标数量 need int(majority_count * target_ratio) - minority_count idx np.where(y_train 1)[0] extra_idx np.random.choice(idx, sizemax(need, 0), replaceTrue) X_train np.vstack([X_train, X_train[extra_idx]]) y_train np.concatenate([y_train, np.ones(len(extra_idx))]) return X_train, y_train # k_neighbors 不能超过少数类样本数 - 1 k min(5, minority_count - 1) smote SMOTE( sampling_strategytarget_ratio, k_neighborsk, random_state42 ) X_res, y_res smote.fit_resample(X_train, y_train) return X_res, y_res逻辑说明函数先统计类别分布判断少数类是否够 SMOTE 用。target_ratio0.3表示把欺诈样本补到正常样本的 30%这个值不是越高越好——补到 1:1 会引入大量合成噪声反而拉低精确率。min_samples6是经验阈值低于 6 个样本时 k 近邻空间太小合成样本几乎和原样本重合没有意义。random_state42保证复现性联邦实验里每个客户端的随机种子最好固定否则聚合结果每次都不一样没法对比。参数怎么调target_ratio建议从 0.1 开始试逐步加到 0.3观察验证集召回率和精确率的平衡点。k_neighbors默认 5样本多的时候可以加到 7 或 9但别超过 10否则合成样本会过度平滑丢失局部特征。3. 从零搭一个 FedAvg SMOTE 的最小可跑系统3.1 环境准备与依赖安装先把环境搭起来。Python 版本建议 3.8 到 3.10太新的版本有些联邦学习库还没适配。核心依赖就四个numpy 做数值计算pandas 读数据scikit-learn 做模型和评估imbalanced-learn 做 SMOTE。# 创建虚拟环境避免污染全局 python -m venv fed_env source fed_env/bin/activate # Windows 用 fed_env\Scripts\activate # 安装核心依赖 pip install numpy pandas scikit-learn imbalanced-learn如果你在 Windows 上遇到 imbalanced-learn 编译报错通常是缺少 C 构建工具直接装预编译 wheel 即可pip install imbalanced-learn --only-binary :all:。这一步卡住的人不少别在这浪费时间。3.2 模拟联邦客户端的数据切分真实联邦场景拿不到多机构数据做实验一般用公开的信用卡欺诈数据集比如 Kaggle 上那个 284807 条记录的 creditcard.csv然后按 Non-IID 方式切给多个客户端。切分策略直接决定实验难度我一般用「按时间切 按类别倾斜」的组合。import pandas as pd import numpy as np from sklearn.model_selection import train_test_split def split_federated_clients(df, n_clients5, fraud_ratio_range(0.1, 0.5)): 把数据切成 n_clients 份每份欺诈比例不同模拟 Non-IID fraud_ratio_range: 每个客户端保留的欺诈样本比例范围 fraud df[df[Class] 1].reset_index(dropTrue) normal df[df[Class] 0].reset_index(dropTrue) clients [] fraud_per_client len(fraud) // n_clients normal_per_client len(normal) // n_clients for i in range(n_clients): # 每个客户端拿到的欺诈样本比例不同 ratio np.random.uniform(*fraud_ratio_range) f_part fraud.iloc[i*fraud_per_client:(i1)*fraud_per_client] f_part f_part.sample(fracratio, random_statei) n_part normal.iloc[i*normal_per_client:(i1)*normal_per_client] client_df pd.concat([f_part, n_part]).sample(frac1, random_statei) clients.append(client_df.reset_index(dropTrue)) return clients逻辑说明先把欺诈和正常样本分开再按客户端数量均分。fraud_ratio_range控制每个客户端保留多少欺诈样本模拟不同机构欺诈发生率不同的现实。random_statei保证每个客户端切分可复现。切完之后每个客户端内部再做 train/test 划分测试集只用于本地评估不参与聚合。参数说明n_clients5是实验常用值太少体现不出联邦的异构性太多单客户端样本不够。fraud_ratio_range我一般设 (0.1, 0.5)让客户端之间的不平衡程度拉开差距这样能测出算法对 Non-IID 的鲁棒性。3.3 FedAvg 聚合主循环这是整个系统的骨架。每一轮服务端把全局模型下发给选中的客户端客户端用本地 SMOTE 后的数据训练若干 epoch回传参数服务端加权平均。import copy import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset def fedavg_train(global_model, clients_data, rounds20, local_epochs3, lr0.01): FedAvg 主循环 rounds: 联邦通信轮数 local_epochs: 每轮本地训练轮数 criterion nn.BCELoss() history [] for r in range(rounds): local_weights [] local_sizes [] for client_df in clients_data: # 本地数据准备 X client_df.drop(Class, axis1).values.astype(np.float32) y client_df[Class].values.astype(np.float32) X_tr, X_te, y_tr, y_te train_test_split( X, y, test_size0.2, stratifyy, random_state42) # 本地 SMOTE X_tr, y_tr local_smote(X_tr, y_tr, target_ratio0.3) # 本地模型从全局模型复制 local_model copy.deepcopy(global_model) optimizer torch.optim.SGD(local_model.parameters(), lrlr) loader DataLoader( TensorDataset(torch.tensor(X_tr), torch.tensor(y_tr)), batch_size256, shuffleTrue) local_model.train() for _ in range(local_epochs): for xb, yb in loader: optimizer.zero_grad() pred local_model(xb).squeeze() loss criterion(pred, yb) loss.backward() optimizer.step() local_weights.append(copy.deepcopy(local_model.state_dict())) local_sizes.append(len(X_tr)) # 加权平均聚合 total sum(local_sizes) global_dict global_model.state_dict() for key in global_dict.keys(): global_dict[key] sum( local_weights[i][key] * (local_sizes[i] / total) for i in range(len(local_weights)) ) global_model.load_state_dict(global_dict) history.append(copy.deepcopy(global_dict)) return global_model, history逻辑说明外层rounds是通信轮数内层local_epochs是本地训练轮数。每个客户端先做 SMOTE 再训练训练完把state_dict存下来。聚合时按local_sizesSMOTE 后的样本数加权这是 FedAvg 的标准做法。注意copy.deepcopy不能省否则本地模型和全局模型共享内存聚合结果会错乱。参数说明rounds20是起步值欺诈检测通常 15 到 30 轮收敛。local_epochs3是 FedAvg 原论文推荐的折中值太大客户端会过拟合本地数据太小全局收敛慢。lr0.01配合 SGD如果用 Adam 可以降到 0.001。batch_size256在几万条样本量级比较稳。3.4 模型结构与评估指标模型不用太复杂欺诈检测的特征维度通常几十维三层全连接足够。关键是评估指标不能只看准确率——不平衡数据下准确率 99.8% 的模型可能一个欺诈都没抓到。class FraudNet(nn.Module): def __init__(self, input_dim): super().__init__() self.net nn.Sequential( nn.Linear(input_dim, 64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 1), nn.Sigmoid() ) def forward(self, x): return self.net(x)评估必须看召回率Recall、精确率Precision和 AUC-ROC。欺诈检测里召回率优先漏掉一个欺诈的代价远大于误判一个正常交易。我一般要求召回率不低于 0.85同时精确率不低于 0.5否则人工复核成本扛不住。Dropout(0.3)是防过拟合的联邦场景下客户端数据少这个不能省。4. 参数调优与避坑那些让实验白跑的细节4.1 数据泄漏与 SMOTE 顺序的三个坑现象测试集 AUC 高达 0.99上线后召回率不到 0.3。原因SMOTE 应用在了全量数据上再划分训练测试集合成样本和原样本高度相似测试集被「污染」。解决严格先划分再 SMOTE测试集永远不碰过采样。这是最常见的翻车点没有之一。现象某个客户端训练直接报错ValueError: Expected n_neighbors n_samples。原因该客户端欺诈样本数少于 k_neighbors 1。解决用 2.3 节的自适应 k 逻辑或者对样本过少的客户端直接跳过 SMOTE 改用类别权重。现象全局模型在部分客户端上表现极差。原因SMOTE 后各客户端样本量差异被放大样本量大的客户端在加权平均里话语权过高。解决聚合权重不要只用样本量可以引入客户端可信度或损失倒数做加权或者对样本量做对数平滑。4.2 联邦聚合的数值稳定性问题现象训练到第 8 轮左右 loss 突然变成 NaN。原因各客户端回传的梯度量级差异太大加权平均后数值溢出。解决本地训练加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)学习率调小或者改用 FedProx 加一个近端项约束本地模型不要偏离全局太远。现象每轮聚合后模型性能不升反降。原因客户端漂移严重平均操作把各自学到的特征互相抵消了。解决减少 local_epochs增加通信轮数让全局模型更频繁地「拉回」本地模型。这是联邦学习里灾难性遗忘的一种表现本地训练太久会忘掉全局知识。4.3 类别不平衡评估的指标陷阱现象准确率 99.9%但混淆矩阵里欺诈类全是 0。原因模型学会了「全预测正常」这个偷懒策略因为这样损失最小。解决损失函数换成带类别权重的 BCE或者用 Focal Loss。SMOTE 只是从数据层面缓解损失层面也要配合。现象验证集召回率波动极大不同随机种子差 20 个点。原因欺诈样本太少验证集本身统计不稳定。解决用分层 K 折交叉验证报告均值和标准差别拿单次结果下结论。联邦场景下还要固定所有客户端的随机种子。提示SMOTE 的sampling_strategy参数在 imbalanced-learn 不同版本里行为有差异0.7 版本之前只接受 float之后支持 dict。跑之前先pip show imbalanced-learn确认版本别照搬网上的老代码。5. 进阶用 FedProx 和动态过采样率把召回率再抬一档基础版跑通之后想再往上走有两个方向值得试。第一个是把 FedAvg 换成 FedProx在本地损失里加一项 μ/2 · ||w - w_global||²约束本地模型不要偏离全局太远。μ 一般设 0.01 到 0.1太大本地学不动太小等于没加。这个改动对 Non-IID 场景的提升很明显尤其是客户端欺诈模式差异大的时候。第二个方向是动态过采样率。固定 target_ratio 的问题是训练前期模型欠拟合需要多补样本训练后期模型已经学到边界再补反而引入噪声。我的做法是按轮次线性衰减前 10 轮用 0.4中间 10 轮降到 0.2最后 5 轮降到 0.1。实测召回率能再涨 3 到 5 个点精确率不掉。def dynamic_ratio(round_idx, total_rounds): 过采样率随轮次衰减 progress round_idx / total_rounds if progress 0.5: return 0.4 elif progress 0.8: return 0.2 else: return 0.1验证方法上别只看最终指标。我习惯每轮记录全局模型在每个客户端测试集上的召回率和 AUC画成曲线看收敛趋势。如果某个客户端的曲线一直往下掉说明它被其他客户端带偏了需要单独排查它的数据分布。另外留一个「从未参与训练的客户端」做泛化测试这是联邦学习里检验全局模型是否真正学到通用特征的关键手段比在训练客户端上刷分有意义得多。最后说个习惯每次改参数之前先 git commit 当前配置跑完对比。联邦学习实验的随机性比单机大得多不记录配置两周后你根本想不起来哪个结果对应哪组参数。这套东西我踩过的坑基本都写在这了从数据切分到聚合稳定性每一步都有翻车的可能。希望帮到你。本文还有配套的精品资源点击获取