☰
信用卡数据集实战:审批模型与欺诈识别为何不能共用一套代码
2026/9/28 5:56:43 网站建设 项目流程

简介:面向机器学习与数据分析初学者的信用卡申请数据实战项目,包含34.81 KB完整数据集和五个精心整理的Python脚本,覆盖数据清洗、可视化、逻辑回归建模、PCA降维、K均值聚类与Isolation Forest异常检测等关键环节。压缩包共7个文件,含5个.py源代码、1个readme.txt和1个csv数据文件,整体仅18KB,轻量易获取;代码全部可运行,依赖pandas、numpy、plotly.express、seaborn、sklearn等标准库,分别演示信用卡申请审批预测、欺诈检测、可疑客户识别与模型快速对比等任务。目前已有96人学习/浏览,适合希望用真实业务数据快速上手机器学习流程的读者。通过对照源码与数据集,可直观理解从特征工程到模型训练评估的完整路径,还能将同样的分析思路迁移至信用风险控制、反欺诈等实际项目中,无论是课程设计、毕业设计还是个人学习,都能从中获得完整实践样例。

1. 信用卡数据集分析预测实战:审批模型与欺诈识别为什么不能共用一套代码

拿到这份信用卡数据集之后,我先干的事不是写模型,而是把五个脚本和 readme 串起来看了一遍。它的思路很清楚:34.81 KB 的 CSV 数据同时喂给两条技术线——一条用逻辑回归和 LazyPredict 做信用卡申请审批预测,另一条用 PCA、KMeans、Isolation Forest 做异常客户识别。两套逻辑从数据预处理开始就分叉了:审批要的是特征可解释性,欺诈要的是离群点敏感性。这份资源适合两种人:一是刚开始接触 sklearn,想用真实 CSV 把完整流程跑通的人;二是想在自己数据集上快速复现"分类 + 异常检测"双方案的从业者。下面我把五个脚本逐个拆开,讲清楚每一步在做什么、参数为什么这么设、以及哪些位置按默认值跑会翻车。

2. 数据清洗与基线建模:先用 LazyPredict 把全模型跑一遍再谈调优

2.1 先读数据:问号占位、缺失值分布和标签分布一起看

经典的 UCI 信用卡审批数据集,特征列一般是脱敏编号 A1~A15,最后一列是 approval 标签。这份 CSV 只有 34.81 KB,行数在几百条级别,跑起来非常快,但字段类型很杂:数值列、类别列、缺失值标记混在一起。第一步不是直接扔给模型,而是先确认缺失值长什么样,再确认标签分布是否均衡。这两个信息决定后面所有预处理策略。

import pandas as pd df = pd.read_csv("data/Credit_Card_Applications.csv", header=None) print(df.shape) print(df.head()) # 经典版本里缺失值用 "?" 占位,读进来是字符串而不是 NaN for col in df.columns: q_count = (df[col] == "?").sum() if q_count > 0: print(f"column {col}: {q_count} missing values") # 标签列分布 print(df.iloc[:, -1].value_counts())

逻辑说明:先用header=None读,因为原文件没有表头;逐列统计?的数量是为了确认缺失值用什么符号占位;最后看标签列分布,判断类别是否平衡。这里最容易犯的错是直接df.isnull().sum(),发现全是 0 就以为数据干净了——实际上缺失值全藏在?里。

参数说明:read_csv的header=None是必须的,不然第一行会被当成列名;后面建模时可以做映射把?统一替换成np.nan,但我更推荐用na_values="?"在读入阶段就转换,少写一行替换逻辑,后续所有列都是干净的数值类型。

替换完成后做一次简单的数据类型审计:数值列转 float,类别列转 category。这一步看起来基础,但对后面的 PCA 和 KMeans 影响很大——这两类算法全是距离计算,类别列不编码、缺失值不填充,距离矩阵就是错的,聚类结果直接没法看。

2.2 LazyPredict 跑基线:十几个分类器一次出结果

在深入逻辑回归之前,先用1-LazyPredict to Classify Credit Card Applications.py跑一遍基线。LazyPredict 是第三方 AutoML 库,作用是自动拟合十几个常见分类器并打印准确率、AUC、训练时间,方便在五分钟内判断"这个数据集的分类天花板大概在哪"。这一步的价值是建立参照系,避免后面调逻辑回归时不知道好坏边界。

# pip install lazypredict from lazypredict.Supervised import LazyClassifier from sklearn.model_selection import train_test_split X = df.iloc[:, :-1].copy() y = df.iloc[:, -1].copy() # 先做特征编码与缺失值填充,LazyPredict 不接受 NaN X = X.replace("?", pd.NA) X = X.apply(pd.to_numeric, errors="coerce") X = X.fillna(X.median()) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) clf = LazyClassifier(verbose=0, ignore_warnings=True, predictions=True) models, predictions = clf.fit(X_train, X_test, y_train, y_test) print(models)

逻辑说明:LazyPredict 的fit接口和 sklearn 不一样,它直接接收训练集和测试集,内部循环遍历分类器并打分,返回一个 DataFrame 和全部分类器的预测结果。我们用它判断哪些模型值得继续调,而不是直接拿结果上线。

参数说明:test_size=0.2是常规划分,样本量只有几百条时不要超过 0.3,否则测试集太小、评估波动大;stratify=y保证划分前后标签比例一致,尤其在类别不平衡时这个参数直接决定测试集是否"失真";random_state=42是固定随机种子,让每次跑出的结果可复现,别人拿到脚本也能复现你的实验。

跑完的基线表大致会长这样:

模型AccuracyROC AUCTime Taken
LogisticRegression0.860.880.01
RandomForestClassifier0.840.850.03
KNeighborsClassifier0.810.790.01
SVC0.800.820.02

基线表的意义不是找冠军模型,而是观察逻辑回归在这个脱敏数据集上不输复杂集成模型——这决定了后面审批预测主脚本用逻辑回归是合理选择,而不是拍脑袋。如果你跑出来某个树模型明显碾压逻辑回归,那后面调参方向就要换。

如果 LazyPredict 安装失败,常见原因是 Python 版本太高,3.11 以上部分依赖包没跟上,我一般降到 3.9 或 3.10 的虚拟环境再装。注意 LazyPredict 只是辅助工具,生产代码里不会依赖它,基线的目的就是快速摸底。

2.3 数据划分的细节:先清洗再切分,顺序错一步就是数据泄漏

train_test_split在模块清单里被单独列出来,确实值得单独讲。常见误用是"先对整个数据集标准化或填充,再切分",这在生产上是数据泄漏——测试集的信息提前进了训练过程,评估结果虚高,上线后真实表现会打回原形。

# 正确顺序:先切分,再做填充和标准化 X_train, X_test, y_train, y_test = train_test_split( X_raw, y, test_size=0.2, random_state=42, stratify=y ) # 填充用训练集的统计量,不能让测试集参与计算 median_vals = X_train.median() X_train = X_train.fillna(median_vals) X_test = X_test.fillna(median_vals) # 标准化同样只用训练集的均值和方差 scaler = StandardScaler().fit(X_train) X_train_scaled = scaler.transform(X_train) X_test_scaled = scaler.transform(X_test)

逻辑说明:fillna和StandardScaler都是用训练集统计量去变换测试集,测试集在这里只负责"被评估",不贡献任何统计信息。这样做的评估结果才是真实线上水平,而不是实验室里的幻觉。

参数说明:median比mean抗离群点,数据里如果有极端值,中位数更稳;StandardScaler默认把数据变成均值 0、方差 1,逻辑回归、KMeans、PCA 都需要这一步,树模型则完全不需要。所以当你看到脚本里同时出现 StandardScaler 和随机森林时,心里要打个问号——树模型不依赖尺度,标准化对它是多余操作。

3. 审批通过率预测:逻辑回归系数就是"审批秘密"最直接的答案

3.1 主脚本的四步管道:编码、填充、标准化、回归

4-Credit Cards Applications.py是资源里的主干脚本,处理的是"该不该批这张卡"的问题。审批决策本质上是一个二分类,标签就是 approved 或 rejected,特征就是申请人的各项资料。脱敏后的特征没有真实业务含义,所以这里追求的不是业务上可解释的规则,而是一个规范、可复现、能交差的分类流程。

import numpy as np import pandas as pd from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X = X.replace("?", pd.NA).apply(pd.to_numeric, errors="coerce") X = X.fillna(X.median()) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) scaler = StandardScaler().fit(X_train) X_train_s = scaler.transform(X_train) X_test_s = scaler.transform(X_test) model = LogisticRegression(C=1.0, max_iter=1000, solver="lbfgs", random_state=42) model.fit(X_train_s, y_train) print("train acc:", model.score(X_train_s, y_train)) print("test acc:", model.score(X_test_s, y_test)) print("test AUC:", roc_auc_score(y_test, model.predict_proba(X_test_s)[:, 1]))

逻辑说明:数据清洗和划分顺序与第 2 章完全一致,强调先切分再变换。逻辑回归在标准化之后的特征上训练,系数大小才能横向比较;未标准化的特征直接按系数绝对值排序没有意义,因为量纲差异会把排序带偏。

参数说明:C=1.0是正则化强度的倒数,C 越小正则越强;样本量小时 C 太大容易过拟合,表现为训练集精度高、测试集掉下来。max_iter=1000是为了防止 lbfgs 在默认 100 次迭代下没收敛就报警告;solver="lbfgs"是中小数据集上逻辑回归的默认首选,适合 L2 正则。predict_proba拿的是概率而不是硬分类,AUC 必须要用概率计算,直接拿model.predict的结果去算会丢失排序信息,AUC 值会偏低。

3.2 审批洞察脚本:系数排序和概率直方图是两道验证工序

2-Credit Card Application Secrets Approval Insi_8639.py名字里带 Secrets,其实就是把"哪些特征在影响审批"这件事量化了。逻辑回归在标准化之后,系数可以直接解释为"该特征每变动一个标准差,对数几率的变化量"。所以按系数绝对值排序,就是特征重要性的粗糙排序,这也是"审批秘密"最直接的答案。

coef_df = pd.DataFrame({ "feature": [f"A{i}" for i in range(1, X.shape[1] + 1)], "coef": model.coef_[0], }) coef_df["abs_coef"] = coef_df["coef"].abs() coef_df = coef_df.sort_values("abs_coef", ascending=False) print(coef_df.head(10)) # 预测概率分布直方图:看模型是否把大部分样本压在一个置信区间 proba = model.predict_proba(X_test_s)[:, 1] import plotly.express as px px.histogram(proba, nbins=20).show()

逻辑说明:系数表回答"哪些特征在驱动审批决策",概率直方图回答"这个模型的输出有没有区分度"。如果直方图集中在 0.4~0.6,说明模型实际上分不开两类样本,这时候再看 AUC 大概率也不好看。

参数说明:model.coef_[0]在二分类中直接取第一行即可,多分类场景才需要遍历;nbins=20控制直方图分箱,样本量几百条时分到 20 箱足够看出分布形状。特征编号 A1~A15 因为脱敏已经不可考,但不妨碍判断"这条审批模型线是否可用"。

到这里审批预测这条线基本闭环了。如果你发现在系数排序里某个特征绝对值特别大,但业务上完全说不过去,先别急着给结论——检查一下这个特征是不是有极端离群值,离群值会拉高系数,但它的"预测力"可能是虚假的。

3.3 类别不平衡的加权处理:class_weight 该什么时候用

审批数据里拒绝样本占大头是很常见的事。如果负样本占到 80% 以上,模型学到的捷径是"全部输出拒绝",准确率照样 0.8 以上,但通过的那几笔一个都没预测出来。这时候光靠调阈值不够,得在模型层面加权。

model = LogisticRegression( C=1.0, max_iter=1000, solver="lbfgs", class_weight="balanced", random_state=42, ) model.fit(X_train_s, y_train) from sklearn.metrics import classification_report print(classification_report(y_test, model.predict(X_test_s)))

逻辑说明:class_weight="balanced"会让 sklearn 自动按类别频率反比加权,少数类样本的错分代价被放大,模型被迫去关注那些占比少但重要的样本。配合classification_report看每个类的 precision、recall、f1,能立刻发现"全拒答"问题是否被缓解。

参数说明:加了class_weight之后整体准确率通常会小幅下降,但少数类的 recall 会明显上升。如果你做的是审批场景,漏掉一个优质客户的代价可能比误拒一个劣质客户更高,优先保 recall 是合理策略;如果是风控场景,误杀太多会导致客诉,优先保 precision。这个取舍没有标准答案,取决于业务,不是代码问题。

4. 避坑:这套代码在真实运行中踩过的五个位置

4.1 缺失值用?占位,isnull 统计永远是 0

现象:跑df.isnull().sum()输出全是 0,但模型一训练就报 "Input contains NaN"。

原因:经典 UCI 信用卡数据集用?字符串表示缺失,pandas 不会自动识别成 NaN。跳过缺失值处理直接建模,数值列被当成字符串,要么报错,要么精度全崩。

解决:pd.read_csv("...", na_values="?")读入时统一转换,或者读完后df.replace("?", pd.NA)再fillna。我在处理这类老数据集时都用na_values在读取阶段解决,后面所有列都是干净的数值类型,排查问题少一层。这个坑藏得深,因为它不报错,只会在模型精度上做文章。

4.2 先标准化再切分导致的"假高分"

现象:测试集准确率 0.92,看起来很漂亮;换一批真实数据,掉到 0.78。

原因:标准化和缺失值填充用了全量数据的统计量,测试集信息泄露进训练流程。这类问题在几百条的小数据集上特别隐蔽——样本少,测试集对统计量的影响更明显,虚高幅度更大。

解决:严格按"切分 → 训练集 fit → 训练集 transform + 测试集 transform"的顺序执行。发现脚本是"先填充再切分"时,重排流程,准确率掉 5 到 8 个百分点是正常的,那个偏低的值才是真实水平。我那一次排查浪费了半天,最后把两步换了个顺序,精度从 0.92 变成 0.85,才意识到前面是数据泄漏。

4.3 Isolation Forest 的 contamination 参数拍脑袋设,异常比例完全跑偏

现象:5-Suspicious client IDs PCA dh Kmean dh IsoForest.py跑出来十几条"可疑客户",人工复核发现里面有大量正常客户,或者反过来一个异常都没抓到。

原因:contamination 默认值是 0.1,意思是"我预期数据里 10% 是异常"。而信用卡申请欺诈的实际比例远低于 1%,0.1 会强行把一堆正常样本标成离群点。这个问题在绝大多数异常检测脚本里都存在,因为 contamination 是唯一告诉模型"数据脏到什么程度"的入口。

解决:先算一下数据里已知欺诈比例,如果有标签就直接统计;没标签就先用 KMeans 或直方图看一眼数据分布,把值压到 0.01~0.05 之间再跑。宁可少抓,也不要误伤一大片——误报带来的业务成本比漏报更高,因为人工复核会淹没在假警报里。

4.4 类别不平衡时只看准确率,模型实际是"全拒答"

现象:准确率 0.86,精细看预测结果全是拒绝,通过的那几笔一笔都没预测出来。

原因:数据里拒绝样本占大头时,模型学会的捷径是"全部输出多数类"。逻辑回归默认分类阈值 0.5 在这种分布下也会加剧问题——少数类的预测概率很难超过 0.5,永远被分到拒绝侧。

解决:输出混淆矩阵和分类报告,重点看少数类的 recall 和 f1-score,而不是 accuracy。必要时用 3.3 节的class_weight="balanced"加权,或者把判定阈值从 0.5 下调到 0.3 再验证。调阈值本身不需要重训模型,直接用predict_proba的概率做比较即可,这个操作在真实项目里非常常用。

4.5 plotly 在无图形界面环境下黑屏不出图

现象:脚本在服务器上跑,px.histogram(...).show()直接不显示,也不报错,进程就挂在那里。

原因:plotly 的show()默认调用浏览器渲染,服务器没有图形界面就没有出口。这不是代码逻辑问题,是运行环境问题,很多人在远程 Linux 上跑这类脚本时都会遇到。

解决:改用fig.write_html("distribution.html")把图写入文件,本地下载后用浏览器看;或者直接用 matplotlib 的Agg后端保存 PNG。我在服务器上处理这类项目时,会把所有出图点都改成写文件,省得来回折腾。这个习惯后来帮我避开了很多次"代码在本地好使、一上服务器就哑火"的尴尬。

5. 进阶:用 Top-K 命中率验证异常检测,再把五条脚本串成一条可复查的流水线

5.1 异常检测的验证:跟分类不一样,不能只看混淆矩阵

欺诈检测脚本输出的是可疑客户 ID 列表,它没有硬标签,所以不能像分类任务那样直接套用混淆矩阵。常见做法是把模型输出的异常得分降序排列,取前 K 个,然后人工复核这 K 个客户里有多少是真的问题账户,这个比例就是 Top-K 命中率。

# 异常得分降序,取前 20 个客户供人工复核 from sklearn.ensemble import IsolationForest iso = IsolationForest(contamination=0.03, random_state=42) iso.fit(X_scaled) scores = iso.score_samples(X_scaled) # 越高越正常,越低越可疑 anomaly_rank = np.argsort(scores)[:20] # 得分最低的 20 个客户 print("top 20 suspicious indices:", anomaly_rank) print("their scores:", scores[anomaly_rank])

逻辑说明:score_samples返回每个样本的异常得分,值越低越可疑;取前 20 是为了给业务方做人工复核,而不是直接当成定罪证据。Top-K 命中率在真实欺诈场景里比 AUC 更贴近使用方式——业务只关心你给的名单准不准,不关心概率曲线下面积有多大。

参数说明:contamination=0.03表示预期 3% 的异常比例,这个值必须按业务调,原理见 4.3 节;random_state=42固定结果,方便下次复跑对比名单是否变化。如果两次跑出的名单差异很大,说明数据或参数不稳,优先检查特征编码和缺失值填充是否一致。

5.2 把五个脚本串成一条流水线:先立项再动手,结果才可复查

这个包的五个脚本其实是三个独立任务:基线对比、审批预测、异常识别。我一般拿到这类项目会先给数据做一次全面体检,然后定一个主指标——审批线看测试集 AUC,欺诈线看 Top-K 命中率。接下来所有调参都围绕主指标进行,不被训练集准确率带偏。每个脚本开头固定random_state,结尾输出可保存的结果文件,预测结果、系数表、可疑客户名单各存一份,这样第二天回来能清楚知道上一次跑出了什么、改了什么参数。

这是我反复踩坑换来的习惯。早先我拿到类似数据集,上来就调参,调完发现不知道哪个版本的结果是准的,只能全部重跑。从那以后我每次做这类项目都强制走一遍流程:先读数据看缺失值和分布,再切分固定种子,然后标准化,最后才进模型。KMeans 聚类数和 Isolation Forest 的 contamination 是两个最需要人工判断的口子,别的参数用默认值先跑,等主指标不达标再动。这套流程帮我在几个项目里省掉了大量返工时间,也希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询