1. 从两个标题说起:这个毕设到底要解决什么问题
第一次看到这个题目的时候,我脑子里冒出来的第一个念头是——这不像一个普通的本科毕设,它其实是两个强相关课题的合体。前半句"基于多维特征融合的互联网贷款客户聚类与风险预警研究",核心动作是聚类和预警;后半句"基于数据挖掘的互联网贷款用途与风险定价可视化研究",核心动作是用途识别和定价可视化。两条线共用同一份底层数据,但服务的目标完全不同:一条线是给风控看的,一条线是给业务和运营看的。
我这些年帮不少同学看过金融数据挖掘方向的毕设,最常见的翻车方式有两种。一种是数据拿不到,最后只能拿一份Kaggle上的德国信用数据集硬套"互联网贷款"的壳,答辩时被老师一句"你这数据跟互联网贷款有什么关系"问穿。另一种是模型跑通了、AUC刷到0.9,但整个系统只有几个matplotlib的静态图,可视化部分形同虚设,最后被评价"工程完成度不足"。
所以这篇东西我想按真实的落地顺序来讲:数据从哪来、特征怎么融合、聚类和预警怎么串起来、可视化大屏怎么搭、以及那些只有真正跑过一遍的人才会知道的坑。适合正在做同类毕设的计算机、数据科学、金融科技方向的同学,也适合想快速搭一套"数据挖掘+可视化"完整链路的开发者。整套东西的技术栈我建议锁定在Python + pandas + scikit-learn + PyOD + ECharts/Flask这条线上,理由后面会展开说。
先给一个整体认知:这个项目的本质,是把一堆杂乱的贷款申请和行为数据,先"分群"(聚类),再"打分"(风险预警),最后"翻译成人话"(可视化)。分群是为了差异化定价和差异化策略,打分是为了在放款前拦住高风险客户,可视化是为了让不懂模型的人也能做决策。三件事缺一不可,这也是为什么这个题目比单纯的"信用评分模型"要难,也比单纯的"数据可视化大屏"要有含金量。
2. 数据从哪来:互联网贷款数据的真实构成与获取思路
2.1 一份能撑起毕设的贷款数据应该长什么样
很多人一上来就问"有没有现成的互联网贷款数据集",我的回答通常是:有,但几乎没有一份能直接满足你这个题目的全部需求。原因在于,互联网贷款的核心特征是"多源异构"——它同时包含申请信息、征信信息、行为信息、设备信息四大类,而公开数据集往往只覆盖其中一两类。
我按经验给你拆一下这四类数据分别包含什么,你对照着手里的数据看缺哪块:
| 数据类别 | 典型字段 | 作用 | 获取难度 |
|---|---|---|---|
| 申请信息 | 年龄、职业、收入、学历、婚姻、贷款金额、期限、利率 | 基础画像,聚类主特征 | 低 |
| 征信信息 | 历史逾期次数、查询次数、负债率、授信额度使用率 | 风险预警核心特征 | 中 |
| 行为信息 | 登录频次、页面停留、申请时段、修改资料次数 | 反欺诈、行为评分 | 高 |
| 设备信息 | 设备型号、操作系统、IP归属地、是否模拟器 | 团伙识别、异常检测 | 高 |
对于毕设来说,最现实的方案是"公开数据打底 + 合理构造补充"。公开数据我推荐几个方向:Lending Club的公开贷款数据(体量大、字段全,适合做聚类和定价)、UCI的Default of Credit Card Clients(适合做违约预警)、以及各类信贷竞赛的脱敏数据。这些数据虽然不叫"互联网贷款",但字段结构和业务逻辑高度一致,你完全可以在论文里说明"采用公开信贷数据模拟互联网贷款场景"。
2.2 数据清洗里那些不写进论文但必须做的事
数据清洗这一步,论文里通常一句话带过,但实际工作量能占整个项目的40%。我列几个互联网贷款数据特有的坑:
- 金额字段的单位混乱:有的数据年收入单位是元,有的是万元,有的是千元。我见过最离谱的一份数据,同一列里既有"50000"也有"5",不统一量纲直接聚类,结果全被大数值字段带偏。
- 类别字段的编码陷阱:职业、学历这类字段,很多人直接LabelEncoder一编了事,但"本科=1、硕士=2、博士=3"这种有序编码和"职业A=1、职业B=2"这种无序编码混在一起,会误导基于距离的聚类算法。正确做法是:有序字段用序数编码,无序字段用独热编码。
- 缺失值的业务含义:征信字段的缺失往往不是"没填",而是"没有该记录",比如"从未逾期"可能表现为空值。这时候填0比填均值合理得多。这个判断必须结合业务,不能无脑fillna。
提示:清洗完一定要做一次describe()和缺失率统计,把每个字段的分布、极值、缺失比例过一遍。我踩过的坑是,某个字段缺失率高达70%,我填了均值,结果这个字段在聚类里贡献了巨大的噪声,最后不得不整列删掉重跑。
2.3 样本不平衡:预警模型绕不过去的坎
风险预警本质是个二分类问题,而违约样本通常只占5%到15%。如果你直接拿原始数据训练,模型会学会"全部预测为不违约",准确率还能有90%,但召回率惨不忍睹。处理方式有三种,我按推荐度排序:
- SMOTE过采样:在特征空间里合成少数类样本,效果稳定,sklearn的imblearn库一行代码搞定。缺点是可能引入噪声,合成样本不一定符合业务逻辑。
- 类别权重调整:在模型里设置class_weight='balanced',不改数据只改损失函数,最省事,适合逻辑回归和树模型。
- 欠采样:随机丢弃多数类样本,简单但浪费数据,样本量小的时候慎用。
我的经验是,预警模型优先用类别权重,聚类模型不需要处理不平衡(聚类本身不看标签)。这个区分很多人搞混,把SMOTE用在聚类前的数据上,反而破坏了原始分布。
3. 多维特征融合:把散落的字段拧成一股绳
3.1 什么叫"多维特征融合",别被这个词吓到
"多维特征融合"听起来很高大上,说白了就是把来自不同来源、不同量纲、不同语义的特征,整合成一组能一起喂给模型的数值向量。它的难点不在算法,而在"怎么让不同维度的特征公平地参与计算"。
举个具体例子。假设你有三个特征:年龄(18到65)、月收入(3000到50000)、历史逾期次数(0到10)。如果你直接把它们丢进KMeans,欧氏距离会被月收入完全主导——因为它的数值范围最大。年龄差个20岁和收入差个20000,在距离计算里后者权重碾压前者。这就是为什么标准化是特征融合的前置动作。
3.2 三层特征体系的构建逻辑
我建议把特征组织成三个层次,这样论文里也好写,逻辑清晰:
- 基础层(静态画像):年龄、学历、职业、婚姻、收入、贷款金额、期限。这层描述"这个人是谁"。
- 行为层(动态表现):历史逾期、查询次数、负债率、额度使用率、申请频次。这层描述"这个人过去怎么做的"。
- 衍生层(组合特征):负债收入比(负债/收入)、月供收入比(月供/月收入)、查询逾期比(查询次数/逾期次数)。这层是前两层的交叉,往往是最有区分度的特征。
衍生层的构造是拉开差距的地方。我实测下来,单纯用基础层做聚类,客户分群很粗糙;加上衍生层之后,能明显分出"高收入低负债的优质客户"和"高收入高负债的伪优质客户"——这两类人在基础层看起来很像,但风险完全不同。
3.3 标准化与降维:PCA到底该不该用
标准化我一般用StandardScaler(Z-score标准化),把每个特征变成均值0、方差1。对于有极端离群值的字段,RobustScaler(基于中位数和四分位距)更稳。
至于PCA降维,我的态度是:聚类前可以用,预警前慎用。原因是PCA把原始特征线性组合成主成分,虽然降了维、去了共线性,但牺牲了可解释性。聚类是为了分群,分出来的群只要能解释业务含义就行,用PCA没问题;但预警模型需要知道"哪个特征导致高风险",PCA之后你没法跟业务方解释"主成分2偏高意味着什么"。
如果你非要用PCA,建议保留累计方差贡献率85%以上的主成分,并且一定要画出碎石图(scree plot)说明选择依据。这个图在论文里很加分。
from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA scaler = StandardScaler() X_scaled = scaler.fit_transform(X) pca = PCA(n_components=0.85) # 保留85%方差 X_pca = pca.fit_transform(X_scaled) print("保留主成分数:", pca.n_components_) print("各主成分方差贡献率:", pca.explained_variance_ratio_)3.4 特征选择的实操取舍
特征不是越多越好。我见过有同学把能拿到的80多个字段全塞进去,结果聚类效果反而变差,因为噪声特征稀释了有效信号。特征选择我常用两个方法配合:
- 方差过滤:先删掉方差接近0的字段(几乎所有人取值都一样,没有区分度)。
- 基于模型的重要性排序:用随机森林跑一遍,看feature_importances_,保留前20到30个。
对于聚类,还可以用轮廓系数(silhouette score)反过来验证特征组合的好坏——换一组特征跑一次聚类,看轮廓系数有没有提升。这个"用聚类效果反推特征质量"的思路,是很多人没想到的实用技巧。
4. 客户聚类:KMeans、层次聚类和GMM到底选哪个
4.1 先想清楚聚类要回答什么业务问题
技术选型之前先问业务:你聚类是为了什么?互联网贷款场景下,聚类的典型目标是客户分层——把客户分成"优质低风险""潜力成长""高风险""欺诈嫌疑"等几类,然后对不同类群用不同的定价和额度策略。
这就决定了聚类结果必须可解释、可落地。如果聚出来8个群,每个群你都说不清是什么人,那这个聚类就是失败的。所以我的建议是:群数控制在3到5个,宁可粗一点,也要每个群都能用一句话描述清楚。
4.2 三种聚类算法的真实对比
| 算法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| KMeans | 最小化簇内平方和 | 快、简单、易解释 | 需预设K、对离群值敏感、只适合球形簇 | 客户分层主力 |
| 层次聚类 | 自底向上合并 | 不用预设K、有树状图 | 复杂度高、大数据慢 | 探索性分析、确定K |
| GMM | 高斯分布混合 | 软聚类、给概率 | 需预设K、对初始化敏感 | 需要概率输出的场景 |
我的实战组合是:先用层次聚类看树状图确定大致K,再用KMeans做正式聚类,最后用GMM给每个客户输出"属于各群的概率"。这样既有硬分群,又有软概率,论文里能写的东西也多。
4.3 K值怎么定:肘部法和轮廓系数要一起看
定K值是聚类最关键的决策。肘部法(elbow method)看的是簇内平方和(inertia)随K变化的拐点,轮廓系数看的是簇内紧密度和簇间分离度的综合。两个方法经常给出不一致的答案,这时候怎么办?
我的经验是:以轮廓系数为主,肘部法为辅,业务可解释性做最终裁决。如果轮廓系数在K=4时最高,但K=4分出来的群业务上讲不通,那就退而求其次选K=3或K=5。记住,聚类是给业务用的,不是给指标用的。
from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt inertias, sil_scores = [], [] K_range = range(2, 9) for k in K_range: km = KMeans(n_clusters=k, random_state=42, n_init=10) labels = km.fit_predict(X_scaled) inertias.append(km.inertia_) sil_scores.append(silhouette_score(X_scaled, labels)) # 画双轴图:左轴inertia,右轴轮廓系数 fig, ax1 = plt.subplots() ax1.plot(K_range, inertias, 'bo-') ax1.set_xlabel('K') ax1.set_ylabel('Inertia') ax2 = ax1.twinx() ax2.plot(K_range, sil_scores, 'rs-') ax2.set_ylabel('Silhouette') plt.show()4.4 聚类结果的业务解读模板
跑出聚类标签只是开始,真正的功夫在解读。我给你一个我常用的解读框架,直接套:
- 算每个群的各特征均值,做成对比表。
- 找出每个群最突出的特征(偏离整体均值最远的几个)。
- 给每个群起个业务名字,比如"高收入低负债稳健型""低收入高查询激进型"。
- 统计每个群的规模和违约率,验证分群是否有效。
如果某个群的违约率显著高于其他群,恭喜你,这个群就是风险预警的重点监控对象,聚类和预警两条线在这里就打通了。
注意:聚类前一定要固定random_state。KMeans对初始质心敏感,不固定随机种子,每次跑出来的分群都不一样,论文里的结果没法复现,答辩时被要求现场跑一遍就尴尬了。
5. 风险预警:从规则引擎到集成模型的演进
5.1 预警和聚类的关系:别做成两张皮
很多同学把聚类和预警做成两个独立模块,聚类归聚类,预警归预警,最后系统里两块互不相干。这是浪费。正确的做法是把聚类标签作为一个特征喂给预警模型。因为"这个客户属于哪个群"本身就携带了风险信息——高风险群的客户,预警模型应该更容易判他为高风险。
这个技巧叫"聚类特征增强",实现起来很简单:把KMeans输出的labels做独热编码,拼到原始特征后面,再训练预警模型。我实测下来,AUC通常能提升1到3个百分点,而且论文里能讲出"无监督与有监督结合"的故事,比单纯堆模型有深度。
5.2 预警模型的选型:为什么我最终选了XGBoost加逻辑回归
预警模型我试过逻辑回归、决策树、随机森林、XGBoost、LightGBM。结论是:
- 逻辑回归:可解释性最强,系数直接反映特征影响方向,适合做"基准模型"和答辩讲解。
- XGBoost/LightGBM:效果最好,但可解释性差,需要SHAP值来补救。
- 随机森林:介于两者之间,稳定但不够极致。
我的最终方案是双模型并行:逻辑回归做可解释的基准,XGBoost做效果上限,然后用SHAP解释XGBoost的预测。这样论文里既有对比实验,又有可解释性分析,内容饱满。
5.3 评估指标:AUC不是唯一,KS和召回率同样重要
风控领域有个专门的指标叫KS(Kolmogorov-Smirnov),衡量的是模型把好坏客户区分开的最大能力。AUC看整体排序能力,KS看最优切分点的区分度,两个都要报。
更重要的是召回率。在风控场景下,漏掉一个高风险客户(假阴性)的代价,远大于误拒一个低风险客户(假阳性)。所以阈值不能设在0.5,要根据业务成本调整。我一般会把阈值往下调,宁可多拒一点,也要把高风险客户捞出来。
| 指标 | 含义 | 目标值 | 备注 |
|---|---|---|---|
| AUC | 整体排序能力 | >0.75 | 信贷场景0.8以上算优秀 |
| KS | 最大区分度 | >0.3 | 0.4以上很好 |
| 召回率 | 高风险客户被识别比例 | >0.7 | 风控优先 |
| 精确率 | 预测高风险中真高风险比例 | 视业务 | 与召回率权衡 |
5.4 阈值调整的实操方法
阈值不是拍脑袋定的。我的做法是画KS曲线,找到KS最大值对应的阈值,再结合业务成本微调。如果误拒成本高,就往上调阈值;如果漏放成本高,就往下调。这个权衡过程写进论文,能体现你对业务的理解,比单纯报个AUC有说服力得多。
from sklearn.metrics import roc_curve import numpy as np fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba) ks_values = tpr - fpr best_threshold = thresholds[np.argmax(ks_values)] print("最佳阈值:", best_threshold, "对应KS:", max(ks_values))6. 可视化大屏:让不懂模型的人也能做决策
6.1 为什么可视化不是"锦上添花"而是"刚需"
答辩的时候,老师不会盯着你的代码看,他们看的是你的系统能不能用、好不好用。一个只有命令行输出的模型,和一个有交互式大屏的系统,给人的印象天差地别。可视化大屏的作用,是把模型的黑箱结果翻译成业务方能看懂的语言。
6.2 技术选型:ECharts + Flask 是我的首选
可视化方案我对比过几种:
- matplotlib/seaborn:适合论文里的静态图,不适合做交互大屏。
- Pyecharts:Python直接生成ECharts图表,上手快,但定制复杂交互时略吃力。
- ECharts + Flask:前端用ECharts画图,后端Flask提供数据接口,最灵活,也最像真实的企业级方案。
- Streamlit/Dash:快速搭原型神器,但样式定制受限,做出来的东西"一眼Streamlit",不够专业。
我推荐ECharts + Flask。ECharts的图表类型丰富、文档全、社区活跃,Flask轻量易部署。两者结合,前端负责炫,后端负责算,职责清晰。
6.3 大屏应该放哪些图:按"决策链路"组织
大屏不是图表堆砌,要按业务决策的顺序来组织。我一般分四块:
- 总览区:客户总数、放款总额、整体违约率、预警客户数。用大数字卡片(数字翻牌器)展示,一眼看全局。
- 分群区:各客户群的规模占比(饼图)、各群的违约率对比(柱状图)。回答"我的客户都是些什么人"。
- 风险区:风险评分分布(直方图)、高风险客户名单(表格)、特征重要性(横向条形图)。回答"谁有风险、为什么有风险"。
- 定价区:不同客户群的利率分布(箱线图)、贷款用途占比(环形图)。回答"该怎么定价、钱都去哪了"。
6.4 前后端数据交互的关键细节
Flask提供接口时,返回格式统一用JSON,前端ECharts的series.data直接对接。这里有个坑:中文编码。Flask返回JSON时如果不设置ensure_ascii=False,中文会变成\uXXXX的转义字符,前端显示乱码。正确写法:
from flask import Flask, jsonify app = Flask(__name__) @app.route('/api/cluster_stats') def cluster_stats(): data = {"groups": ["优质客户", "潜力客户", "高风险客户"], "counts": [1200, 800, 300]} return jsonify(data) # Flask 2.x默认ensure_ascii=False如果是老版本Flask,需要手动设置app.config['JSON_AS_ASCII'] = False。这个细节不处理,大屏上全是问号和方块,非常影响观感。
6.5 大屏的视觉设计:少即是多
我见过太多毕设大屏,恨不得把所有图表都塞进一屏,结果密密麻麻看不清。我的建议是:
- 配色统一:主色调不超过3种,风险相关用红黄绿三色系,其他用蓝灰。
- 留白充足:图表之间留够间距,别贴边。
- 动效克制:数字翻牌、图表加载动画可以有,但别满屏乱飞。
- 响应式:用rem或vw做单位,保证在不同分辨率下不变形。
提示:大屏分辨率建议按1920x1080设计,这是答辩投影和大多数显示器的标准。用CSS的transform: scale()做整体缩放,适配不同屏幕。
7. 那些只有跑过一遍才知道的坑
7.1 聚类结果每次都不一样
前面提过,KMeans的随机初始化会导致结果不稳定。除了固定random_state,还可以用n_init参数多跑几次取最优。sklearn新版本n_init默认是10,老版本是1,这个差异会导致同样的代码在不同环境结果不同,务必注意版本。
7.2 特征量纲没统一导致聚类失效
这是新手最常犯的错。收入是万级,年龄是十级,不标准化直接聚类,结果全被收入主导。记住:任何基于距离的算法,用之前先标准化。KMeans、层次聚类、KNN、SVM都适用这条。
7.3 预警模型过拟合
训练集AUC 0.95,测试集AUC 0.7,这是典型的过拟合。原因通常是特征太多、样本太少、树太深。解决办法:交叉验证、限制树深度、加正则化、减少特征。我一般用5折交叉验证,看训练和验证的差距,差距超过0.1就要警惕。
7.4 可视化大屏数据不刷新
很多同学的毕设大屏是"死"的,数据写死在代码里。正确做法是前端定时轮询后端接口(setInterval),或者用WebSocket做实时推送。哪怕数据是模拟的,也要让它动起来,答辩时演示效果完全不一样。
7.5 论文和代码对不上
这是最致命的。论文里写"采用GMM聚类",代码里却是KMeans;论文里说"特征30个",代码里实际用了50个。答辩老师如果较真,让你现场跑代码,直接穿帮。我的建议是:论文定稿前,把代码从头到尾跑一遍,逐条核对论文里的每个数字、每个方法名。
8. 项目落地节奏与个人经验
如果你现在刚开始做这个毕设,我给你一个我验证过的节奏:
第一周,搞定数据。别贪多,一份能跑通的数据胜过十份躺在硬盘里的数据。第二到三周,做特征工程和聚类,把分群结果和业务解读写出来。第四到五周,做预警模型,重点在评估和阈值调整。第六周,搭可视化大屏,前后端联调。最后一周,写论文、核对代码、准备答辩。
我个人在实际操作中的体会是,这个项目最大的价值不在于模型多复杂,而在于整条链路的完整性——从原始数据到分群、到预警、到可视化决策,每一环都打通。很多同学卡在某一环死磕算法精度,反而忽略了整体。答辩老师更看重的是你能不能把一个完整的故事讲清楚:数据怎么来的、客户怎么分的、风险怎么预警的、结果怎么呈现的。
最后分享一个小技巧:答辩前准备一个"演示脚本",把从数据加载到出图的整个流程写成一个可一键运行的脚本,现场演示时不用手忙脚乱地敲命令。这个细节能极大提升答辩的流畅度,也是我当年踩过坑之后总结出来的。