邦學(xué)習(xí)實(shí)戰(zhàn):FedAvg與SMOTE實(shí)現(xiàn)信用卡欺詐檢測)
簡介這份資源面向計(jì)算機(jī)、人工智能及相關(guān)專業(yè)的學(xué)生與開發(fā)者提供一套基于FedAvg聯(lián)邦學(xué)習(xí)算法與SMOTE過采樣優(yōu)化的聯(lián)邦信用卡欺詐交易檢測完整項(xiàng)目源碼。項(xiàng)目通過構(gòu)建Server與Clients對象模擬真實(shí)場景下服務(wù)器與節(jié)點(diǎn)間的雙向參數(shù)傳遞在保護(hù)各銀行數(shù)據(jù)隱私、避免數(shù)據(jù)集共享的前提下提升欺詐檢測效果適合作為畢業(yè)設(shè)計(jì)、課程設(shè)計(jì)或算法進(jìn)階練習(xí)。壓縮包共8個(gè)文件約43.14MB包含5個(gè)Python源碼文件、1個(gè)Markdown說明文檔、1張流程示意圖和1份信用卡交易數(shù)據(jù)集分別對應(yīng)模型定義、服務(wù)端與客戶端邏輯、數(shù)據(jù)處理及項(xiàng)目說明等模塊。目前已有173人學(xué)習(xí)下載。代碼經(jīng)過完整測試運(yùn)行讀者可據(jù)此理解聯(lián)邦學(xué)習(xí)參數(shù)聚合流程、SMOTE類別不平衡處理思路以及整體工程結(jié)構(gòu)并可在現(xiàn)有基礎(chǔ)上修改擴(kuò)展用于畢設(shè)、課設(shè)或項(xiàng)目初期立項(xiàng)演示。1. 聯(lián)邦信用卡欺詐檢測為什么FedAvg加SMOTE值得你花時(shí)間信用卡欺詐檢測是典型的高不平衡二分類任務(wù)正常交易占比動(dòng)輒 99.5% 以上欺詐樣本可能只有千分之幾。更棘手的是真實(shí)場景下交易數(shù)據(jù)分散在多家銀行或支付機(jī)構(gòu)手里出于合規(guī)和商業(yè)競爭誰都不愿意把原始流水匯總到一處訓(xùn)練。聯(lián)邦學(xué)習(xí)正好切中這個(gè)痛點(diǎn)各方只交換模型參數(shù)不交換數(shù)據(jù)。但聯(lián)邦場景下非獨(dú)立同分布Non-IID和極端類別不平衡會(huì)同時(shí)放大——有的節(jié)點(diǎn)欺詐樣本多有的幾乎全是正常交易全局模型很容易被多數(shù)類帶偏。FedAvg 負(fù)責(zé)聚合各節(jié)點(diǎn)梯度SMOTE 負(fù)責(zé)在本地把少數(shù)類過采樣到合理比例兩者組合是目前工業(yè)界落地聯(lián)邦風(fēng)控最常見的一條路徑。這篇筆記面向有 Python 基礎(chǔ)、想把這套方案跑通并調(diào)穩(wěn)的工程師從數(shù)據(jù)劃分、本地過采樣、聚合策略一路講到踩坑排查代碼可直接復(fù)現(xiàn)。2. FedAvg 與 SMOTE 的協(xié)作機(jī)制先搞清楚誰在什么時(shí)候起作用2.1 FedAvg 的聚合邏輯與信用卡場景的適配點(diǎn)FedAvgFederated Averaging的核心思想很樸素每個(gè)參與方在本地用自己數(shù)據(jù)跑若干輪 SGD把更新后的模型參數(shù)上傳到中心服務(wù)器服務(wù)器按樣本量加權(quán)平均再把全局模型下發(fā)。公式上就是 $w_{t1} \sum_{k1}^{K} \frac{n_k}{n} w_{t1}^k$其中 $n_k$ 是第 k 個(gè)節(jié)點(diǎn)的樣本數(shù)。放到信用卡欺詐檢測里這個(gè)加權(quán)方式有個(gè)隱含假設(shè)樣本多的節(jié)點(diǎn)貢獻(xiàn)大。但欺詐檢測中樣本多的節(jié)點(diǎn)往往是正常交易多欺詐占比反而低直接按總量加權(quán)會(huì)讓全局模型更偏向多數(shù)類。常見做法是改成按欺詐樣本量加權(quán)或者對每個(gè)節(jié)點(diǎn)的損失做類別加權(quán)后再聚合。我一般會(huì)在聚合前先統(tǒng)計(jì)各節(jié)點(diǎn)的正負(fù)樣本比如果差異超過一個(gè)數(shù)量級就手動(dòng)調(diào)整權(quán)重。另一個(gè)適配點(diǎn)是通信輪次。信用卡交易數(shù)據(jù)量大但欺詐模式變化快聯(lián)邦訓(xùn)練不能像圖像分類那樣跑幾百輪。實(shí)測下來20 到 50 輪全局聚合通常就能收斂再多了收益遞減還容易過擬合。每輪本地 epoch 控制在 1 到 3 之間太多會(huì)導(dǎo)致本地模型偏離全局太遠(yuǎn)聚合時(shí)震蕩。2.2 SMOTE 在聯(lián)邦節(jié)點(diǎn)上的正確打開方式SMOTESynthetic Minority Over-sampling Technique通過在少數(shù)類樣本之間做線性插值生成合成樣本。標(biāo)準(zhǔn)流程是對每個(gè)少數(shù)類樣本找它的 k 個(gè)最近鄰少數(shù)類樣本隨機(jī)選一個(gè)在兩者連線上隨機(jī)取一點(diǎn)作為新樣本。放到聯(lián)邦場景關(guān)鍵問題是SMOTE 只能在本地做不能跨節(jié)點(diǎn)生成合成樣本否則就泄露了其他節(jié)點(diǎn)的數(shù)據(jù)分布。本地執(zhí)行時(shí)我一般把欺詐樣本過采樣到占總樣本的 10% 到 30% 之間。太低起不到平衡作用太高會(huì)引入大量合成噪聲模型在真實(shí)欺詐上的召回反而下降。k 近鄰數(shù)默認(rèn) 5如果本地欺詐樣本少于 50 條建議降到 3 甚至 2否則近鄰本身就不具代表性。還有一個(gè)細(xì)節(jié)SMOTE 之前一定要先做特征標(biāo)準(zhǔn)化因?yàn)樗腔跉W氏距離找近鄰的量綱不統(tǒng)一會(huì)讓距離計(jì)算完全失真。from imblearn.over_sampling import SMOTE from sklearn.preprocessing import StandardScaler import numpy as np def local_smote(X, y, target_ratio0.2, k_neighbors5): 本地節(jié)點(diǎn)執(zhí)行SMOTE過采樣 X: 特征矩陣 (n_samples, n_features) y: 標(biāo)簽 (n_samples,) target_ratio: 少數(shù)類目標(biāo)占比 k_neighbors: SMOTE近鄰數(shù) scaler StandardScaler() X_scaled scaler.fit_transform(X) n_majority np.sum(y 0) n_minority_target int(n_majority * target_ratio / (1 - target_ratio)) n_minority_current np.sum(y 1) if n_minority_current n_minority_target: return X_scaled, y, scaler # 動(dòng)態(tài)調(diào)整k值防止少數(shù)類樣本過少時(shí)報(bào)錯(cuò) actual_k min(k_neighbors, n_minority_current - 1) if actual_k 1: return X_scaled, y, scaler smote SMOTE( sampling_strategy{1: n_minority_target}, k_neighborsactual_k, random_state42 ) X_res, y_res smote.fit_resample(X_scaled, y) return X_res, y_res, scaler這段代碼做了三件事先標(biāo)準(zhǔn)化保證距離計(jì)算合理再根據(jù)多數(shù)類數(shù)量反推少數(shù)類目標(biāo)數(shù)量最后動(dòng)態(tài)調(diào)整 k 值防止樣本過少崩潰。注意sampling_strategy用的是字典形式只對標(biāo)簽 1 過采樣不動(dòng)多數(shù)類。返回的 scaler 要保存下來推理時(shí)對新數(shù)據(jù)做同樣的變換。2.3 聯(lián)邦訓(xùn)練主循環(huán)的代碼骨架把 FedAvg 和本地 SMOTE 串起來主循環(huán)大致長這樣import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset 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) def federated_train(clients, global_rounds30, local_epochs2, lr0.001): clients: 各節(jié)點(diǎn)的 (X, y) 列表 input_dim clients[0][0].shape[1] global_model FraudNet(input_dim) global_weights global_model.state_dict() for round_idx in range(global_rounds): local_weights_list [] local_sizes [] for X_local, y_local in clients: # 本地SMOTE X_res, y_res, _ local_smote(X_local, y_local) # 本地訓(xùn)練 local_model FraudNet(input_dim) local_model.load_state_dict(global_weights) optimizer torch.optim.Adam(local_model.parameters(), lrlr) criterion nn.BCELoss() dataset TensorDataset( torch.FloatTensor(X_res), torch.FloatTensor(y_res).unsqueeze(1) ) loader DataLoader(dataset, batch_size64, shuffleTrue) local_model.train() for epoch in range(local_epochs): for batch_x, batch_y in loader: optimizer.zero_grad() pred local_model(batch_x) loss criterion(pred, batch_y) loss.backward() optimizer.step() local_weights_list.append(local_model.state_dict()) local_sizes.append(len(X_res)) # FedAvg聚合按本地過采樣后的樣本量加權(quán) total sum(local_sizes) new_weights {} for key in global_weights.keys(): new_weights[key] sum( local_weights_list[i][key] * (local_sizes[i] / total) for i in range(len(local_weights_list)) ) global_weights new_weights global_model.load_state_dict(global_weights) return global_model聚合時(shí)用的是過采樣后的樣本量做權(quán)重這樣欺詐樣本多的節(jié)點(diǎn)話語權(quán)更大緩解了 Non-IID 下的偏置。local_epochs設(shè) 2 是折中設(shè) 1 收斂慢設(shè) 5 以上本地模型容易跑偏。學(xué)習(xí)率 0.001 配 Adam 是比較穩(wěn)的起點(diǎn)如果 loss 震蕩就降到 0.0005。3. 從原始交易流水到聯(lián)邦節(jié)點(diǎn)數(shù)據(jù)預(yù)處理與劃分實(shí)操3.1 特征工程里最容易被忽略的四個(gè)字段信用卡欺詐檢測的公開數(shù)據(jù)集比如 Kaggle 上的 creditcard.csv通常有 30 個(gè)左右特征其中 V1 到 V28 是 PCA 降維后的匿名特征Time 是交易時(shí)間戳Amount 是金額。實(shí)際落地時(shí)我一般會(huì)額外構(gòu)造幾個(gè)字段交易時(shí)間的小時(shí)數(shù)欺詐在凌晨 2 到 5 點(diǎn)明顯高發(fā)、金額的對數(shù)值原始金額長尾太嚴(yán)重、以及該卡在過去 1 小時(shí)內(nèi)的交易頻次。這三個(gè)衍生特征在聯(lián)邦場景下也能本地計(jì)算不涉及跨節(jié)點(diǎn)數(shù)據(jù)。Time 字段要轉(zhuǎn)成小時(shí)和星期幾兩個(gè)維度直接丟原始秒數(shù)沒有意義。Amount 做 log1p 變換后再標(biāo)準(zhǔn)化。V 系列特征本身已經(jīng)做過 PCA不需要再處理但要注意不同節(jié)點(diǎn)的 V 特征分布可能有偏移聯(lián)邦訓(xùn)練前最好各節(jié)點(diǎn)獨(dú)立做一次標(biāo)準(zhǔn)化而不是用全局統(tǒng)計(jì)量。3.2 非獨(dú)立同分布劃分模擬真實(shí)銀行間的數(shù)據(jù)差異做實(shí)驗(yàn)時(shí)不能把數(shù)據(jù)隨機(jī)切幾份就當(dāng)聯(lián)邦節(jié)點(diǎn)那樣是 IID 的體現(xiàn)不出 FedAvg 的真實(shí)難度。常見做法是按時(shí)間切分每個(gè)節(jié)點(diǎn)取不同時(shí)間段的交易這樣欺詐模式隨時(shí)間漂移節(jié)點(diǎn)間分布自然不同。另一種是按金額分桶有的節(jié)點(diǎn)專做小額高頻有的做大額低頻。我一般兩種都試時(shí)間切分更接近真實(shí)場景。import pandas as pd import numpy as np def split_non_iid(df, n_clients5, strategytime): 非獨(dú)立同分布劃分 strategy: time 按時(shí)間分段, amount 按金額分桶 df df.sort_values(Time).reset_index(dropTrue) if strategy time: chunk_size len(df) // n_clients clients [] for i in range(n_clients): start i * chunk_size end (i 1) * chunk_size if i n_clients - 1 else len(df) clients.append(df.iloc[start:end]) return clients elif strategy amount: df[amount_bin] pd.qcut(df[Amount], n_clients, labelsFalse) clients [df[df[amount_bin] i].drop(amount_bin, axis1) for i in range(n_clients)] return clients時(shí)間切分后每個(gè)節(jié)點(diǎn)的欺詐率可能從 0.1% 到 1% 不等這種差異正是 FedAvg 需要扛住的。劃分完要檢查每個(gè)節(jié)點(diǎn)的正負(fù)樣本比如果某個(gè)節(jié)點(diǎn)欺詐樣本少于 20 條SMOTE 的 k 值要相應(yīng)調(diào)小或者考慮在該節(jié)點(diǎn)上不做過采樣、只參與聚合。3.3 本地驗(yàn)證集不能省聯(lián)邦場景下的評估陷阱很多聯(lián)邦學(xué)習(xí)代碼只留訓(xùn)練集靠全局模型在中心服務(wù)器上的測試集評估。但中心服務(wù)器沒有數(shù)據(jù)真實(shí)場景下你只能在各節(jié)點(diǎn)本地評估。我一般每個(gè)節(jié)點(diǎn)留 20% 做本地驗(yàn)證訓(xùn)練時(shí)用 SMOTE 后的數(shù)據(jù)驗(yàn)證時(shí)用原始未過采樣的數(shù)據(jù)這樣評估指標(biāo)才反映真實(shí)分布。評估指標(biāo)別只看準(zhǔn)確率不平衡場景下準(zhǔn)確率沒有意義。重點(diǎn)看 AUC-ROC、召回率Recall和 F1。欺詐檢測里召回率通常比精確率重要漏掉一筆欺詐的代價(jià)遠(yuǎn)大于誤攔一筆正常交易。如果業(yè)務(wù)上誤攔成本也高就調(diào)閾值讓精確率和召回率平衡一般閾值設(shè)在 0.3 到 0.5 之間不是默認(rèn)的 0.5。4. 避坑與排查聯(lián)邦信用卡欺詐檢測的五個(gè)血淚教訓(xùn)4.1 現(xiàn)象全局模型 AUC 比單節(jié)點(diǎn)本地訓(xùn)練還低原因FedAvg 按總樣本量加權(quán)正常交易多的節(jié)點(diǎn)主導(dǎo)了聚合方向欺詐特征被稀釋。解決改成按欺詐樣本量加權(quán)或者對每個(gè)節(jié)點(diǎn)的損失函數(shù)加類別權(quán)重pos_weight讓少數(shù)類梯度更大。實(shí)測按欺詐樣本量加權(quán)后 AUC 能回升 3 到 5 個(gè)百分點(diǎn)。4.2 現(xiàn)象SMOTE 之后模型在驗(yàn)證集上召回率暴跌原因合成樣本在特征空間里侵入了多數(shù)類區(qū)域模型學(xué)到了假的決策邊界。解決SMOTE 之前先做特征選擇把區(qū)分度低的 V 特征去掉過采樣比例從 0.2 降到 0.1 試試或者改用 Borderline-SMOTE只在邊界附近的少數(shù)類樣本上生成。我一般會(huì)畫一下過采樣前后少數(shù)類樣本的 t-SNE 分布肉眼確認(rèn)合成樣本沒有跑到多數(shù)類堆里。4.3 現(xiàn)象訓(xùn)練到第 10 輪左右 loss 突然變成 NaN原因各節(jié)點(diǎn)標(biāo)準(zhǔn)化方式不一致聚合后的權(quán)重對應(yīng)了不同尺度的特征前向傳播數(shù)值爆炸。解決統(tǒng)一標(biāo)準(zhǔn)化流程所有節(jié)點(diǎn)用相同的StandardScaler邏輯各自 fit 但變換方式一致或者在聚合后對權(quán)重做裁剪torch.nn.utils.clip_grad_norm_。學(xué)習(xí)率也要檢查聯(lián)邦場景下 0.001 以上容易炸。4.4 現(xiàn)象某些節(jié)點(diǎn)訓(xùn)練幾輪后直接掉線全局模型性能波動(dòng)大原因節(jié)點(diǎn)本地欺詐樣本太少SMOTE 生成的合成樣本質(zhì)量差本地模型輸出噪聲大聚合時(shí)拖累全局。解決設(shè)一個(gè)參與門檻本地欺詐樣本少于 30 條的節(jié)點(diǎn)只做推理不參與聚合或者對這些節(jié)點(diǎn)降低聚合權(quán)重。另外可以加一個(gè)異常檢測某節(jié)點(diǎn)上傳的權(quán)重與全局權(quán)重余弦相似度低于 0.5 就丟棄該輪更新。4.5 現(xiàn)象本地驗(yàn)證 AUC 很高但上線后實(shí)際欺詐召回率很低原因驗(yàn)證集和訓(xùn)練集來自同一時(shí)間段沒有體現(xiàn)時(shí)間漂移或者 SMOTE 只在訓(xùn)練時(shí)做了驗(yàn)證集沒做導(dǎo)致評估偏樂觀。解決驗(yàn)證集按時(shí)間往后切用未來數(shù)據(jù)評估當(dāng)前模型評估時(shí)用原始分布不要對驗(yàn)證集過采樣上線后持續(xù)監(jiān)控召回率低于閾值就觸發(fā)重新訓(xùn)練。這個(gè)坑我在實(shí)際項(xiàng)目里踩過離線 AUC 0.95上線召回只有 0.6后來加了時(shí)間切分驗(yàn)證才暴露出來。5. 進(jìn)階技巧讓 FedAvg 加 SMOTE 在真實(shí)風(fēng)控里跑得更穩(wěn)5.1 用焦點(diǎn)損失替代交叉熵緩解過采樣后的噪聲敏感SMOTE 生成的合成樣本不可避免帶有噪聲標(biāo)準(zhǔn) BCE Loss 對噪聲敏感。焦點(diǎn)損失Focal Loss通過調(diào)制因子 $(1-p_t)^\gamma$ 降低易分類樣本的權(quán)重讓模型聚焦在難樣本上。在聯(lián)邦場景下每個(gè)節(jié)點(diǎn)本地用 Focal Loss聚合時(shí)依然按樣本量加權(quán)。實(shí)測 $\gamma2$ 時(shí)合成噪聲帶來的假陽性明顯減少。class FocalLoss(nn.Module): def __init__(self, alpha0.25, gamma2.0): super().__init__() self.alpha alpha self.gamma gamma def forward(self, pred, target): bce nn.functional.binary_cross_entropy(pred, target, reductionnone) pt torch.exp(-bce) focal self.alpha * (1 - pt) ** self.gamma * bce return focal.mean()alpha控制正負(fù)樣本權(quán)重欺詐檢測里設(shè) 0.25 到 0.5 之間gamma控制難易樣本的聚焦程度2.0 是常用值。替換掉主循環(huán)里的BCELoss即可其他流程不變。5.2 動(dòng)態(tài)調(diào)整過采樣比例根據(jù)每輪全局召回率反饋固定過采樣比例在訓(xùn)練初期有效但后期可能過擬合。我一般會(huì)加一個(gè)反饋機(jī)制每輪聚合后在各節(jié)點(diǎn)本地驗(yàn)證集上算召回率如果召回率連續(xù) 3 輪不升就把下一輪的 SMOTE 目標(biāo)比例降 0.05最低降到 0.05。這樣訓(xùn)練后期合成樣本減少模型更多依賴真實(shí)欺詐樣本泛化更好。5.3 通信壓縮讓聯(lián)邦訓(xùn)練在真實(shí)網(wǎng)絡(luò)下可行每輪上傳完整模型參數(shù)在節(jié)點(diǎn)多、模型大時(shí)通信開銷很高。常見做法是只上傳梯度變化最大的 top-k 參數(shù)或者做量化float32 轉(zhuǎn) int8。信用卡欺詐檢測模型通常不大幾萬參數(shù)壓縮收益有限但如果節(jié)點(diǎn)數(shù)超過 50 個(gè)建議至少做一輪 top-50% 稀疏化。注意壓縮后聚合要對應(yīng)還原否則權(quán)重對不上。技巧適用場景預(yù)期收益Focal Loss合成樣本噪聲大假陽性降 10%-20%動(dòng)態(tài)過采樣比例訓(xùn)練后期過擬合召回率穩(wěn)中有升top-k 稀疏化節(jié)點(diǎn)數(shù) 50通信量降 50%按欺詐樣本量加權(quán)節(jié)點(diǎn)間不平衡嚴(yán)重AUC 升 3-5 個(gè)點(diǎn)這些技巧不用全上按你的節(jié)點(diǎn)規(guī)模和數(shù)據(jù)分布選兩三個(gè)就行。我自己的習(xí)慣是先把基礎(chǔ) FedAvg 加 SMOTE 跑通看到 baseline 指標(biāo)后再逐個(gè)加每加一個(gè)記錄一次驗(yàn)證集 AUC 和召回漲了就留沒漲就撤。聯(lián)邦學(xué)習(xí)調(diào)參比單機(jī)訓(xùn)練玄學(xué)得多同樣的配置換個(gè)隨機(jī)種子結(jié)果可能差兩個(gè)點(diǎn)所以每次實(shí)驗(yàn)至少跑三遍取平均。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取