互联网贷款客户聚类与风险预警可视化毕设实战指南 1. 从两个标题说起这个毕设到底要解决什么问题先把标题拆开看。这个项目其实包含了两条主线一条是“基于多维特征融合的互联网贷款客户聚类与风险预警研究”另一条是“基于数据挖掘的互联网贷款用途与风险定价可视化研究”。很多同学看到这种双标题会懵不知道到底该做哪一个。我的理解是这两个标题本质上是同一个业务闭环的两个阶段——先用聚类把客户分群、识别风险再用可视化的方式把贷款用途和风险定价的逻辑呈现出来。前者是“算”后者是“看”合在一起才是一个完整的信贷风控分析系统。互联网贷款这个场景数据维度天然就多。用户的基本属性年龄、职业、收入区间、行为数据申请频次、还款记录、逾期天数、设备信息登录设备数、IP归属地变化、征信衍生变量多头借贷指数、负债收入比等等随便一列就是几十个字段。如果直接把这些字段丢进模型维度灾难先不说业务上也解释不通。所以“多维特征融合”这个点核心要解决的是怎么把散落在不同表、不同粒度、不同量纲的特征整合成一套能用于聚类和预警的输入矩阵。风险预警和风险定价又是两回事。预警关注的是“这个客户会不会逾期”是一个二分类或者异常检测问题定价关注的是“这个客户应该收多少利率才合理”是一个回归或者分群映射问题。很多毕设把这两个混在一起做最后逻辑就乱了。我的建议是聚类负责分群分群结果同时服务于预警和定价——高风险群进预警名单不同风险等级的群映射到不同的利率区间。这样两条线就串起来了。可视化部分热词里出现了ECharts、百度可视化大屏、企业级数据可视化这些词说明大家对这个项目的期待不只是跑个模型出个AUC而是要有一个能看、能讲、能演示的前端界面。这一点在毕设答辩里非常关键评委老师不一定懂你的GMM推导但他一定看得懂大屏上跳动的风险分布图。这个项目适合谁如果你是计算机、数据科学、统计相关专业的本科或硕士正在找毕设题目或者你已经选了类似方向但不知道从哪下手那这篇内容就是给你写的。我会从数据构造、特征工程、聚类选型、预警建模、可视化落地这几个环节把每个关键决策背后的“为什么”讲清楚同时给出可以直接复现的代码骨架和踩坑经验。2. 数据从哪来没有真实信贷数据时怎么构造一份能跑通的样本2.1 公开数据集的可用性与局限做互联网贷款相关的毕设第一个卡点永远是数据。真实信贷数据涉及用户隐私不可能拿到。那怎么办常见的替代方案有三类一是用Lending Club或Prosper的公开贷款数据这两个数据集在Kaggle上都能找到字段比较全有贷款金额、利率、期限、借款人收入、FICO分数、逾期状态等二是用德国信用数据集German Credit Data或澳大利亚信用数据集规模小但干净适合做算法验证三是自己用Python的Faker库合成数据完全可控但真实性差。我的建议是主实验用Lending Club的一个子集比如2008-2015年的已完结贷款因为它的字段结构和互联网贷款最接近有利率int_rate、贷款用途purpose、风险等级grade/sub_grade这些关键列。然后用合成数据做补充实验验证模型在不同分布下的稳定性。这样既保证了真实性又能在答辩时说明“我有做鲁棒性测试”。Lending Club数据有个坑要注意它的利率字段是字符串格式带百分号比如“13.56%”需要先去掉百分号再转float。另外贷款状态loan_status有好几种取值做二分类预警时通常把“Charged Off”和“Default”归为坏样本“Fully Paid”归为好样本“Current”和“Late”这类中间状态要么剔除要么单独处理。我一般会剔除“Current”因为它的最终结果还没确定强行标注会引入噪声。2.2 合成数据的字段设计思路如果要用合成数据字段设计不能瞎编要贴合互联网贷款的业务逻辑。我通常会构造这么几组变量用户基础属性年龄18-65、月收入3000-50000、学历有序分类、婚姻状态、职业类型。借贷行为特征历史借款次数、历史逾期次数、当前待还笔数、平均借款金额、借款期限偏好。征信衍生特征多头借贷指数近3个月申请平台数、负债收入比DTI、信用历史长度月。设备与行为特征登录设备数、近7天登录次数、申请时段是否深夜、IP归属地变更次数。这些字段之间要有相关性不能完全独立生成。比如收入越高DTI通常越低逾期次数越多多头借贷指数往往越高。可以用Copula或者简单的线性加噪声的方式来生成相关变量。这样聚类出来的群才有业务意义不然就是一堆随机点的机械分割。提示合成数据一定要固定随机种子比如np.random.seed(42)否则每次跑出来的聚类结果都不一样答辩演示时会很尴尬。2.3 数据清洗中容易被忽略的细节不管用哪种数据清洗环节有几个坑是高频出现的。第一缺失值处理不能一刀切用均值填充。比如“月收入”缺失用均值填充会扭曲低收入和高收入两端的分布更好的做法是分群填充或者用模型预测填充。第二异常值要区分“错误值”和“真实极端值”。比如年龄填了200岁这是错误值直接删但月收入填了50万可能是真实的高净值用户不能随便删可以考虑截断或者单独分群。第三类别型变量的编码方式会影响聚类距离的计算。如果直接用LabelEncoder把“职业类型”编成0-9聚类算法会认为职业8和职业9的距离比职业0和职业1更近这显然不合理。正确的做法是用One-Hot编码或者目标编码。3. 多维特征融合把几十个字段拧成一股绳3.1 特征融合到底融合什么“多维特征融合”这个词听起来很学术拆开看其实就三件事特征对齐、特征变换、特征降维。特征对齐解决的是不同表之间粒度不一致的问题比如用户属性表是一人一行但行为日志表是一人多行需要先聚合到用户粒度。特征变换解决的是量纲和分布问题比如收入是万元级逾期次数是个位数直接放一起聚类收入会主导距离计算。特征降维解决的是维度灾难和共线性问题几十个特征里有很多是高度相关的比如“近3个月申请平台数”和“近6个月申请平台数”留着两个只会增加噪声。我一般会按这个流程走先把所有特征按用户ID聚合然后做缺失值填充和异常值处理接着对连续变量做标准化Z-Score或RobustScaler对类别变量做One-Hot最后用PCA或者特征重要性筛选做降维。注意标准化要在训练集上fit然后transform到测试集不能全量fit否则会有数据泄露。3.2 连续变量的标准化Z-Score还是RobustScaler这个问题看起来小但对聚类结果影响很大。Z-Score标准化后数据的均值是0标准差是1适合近似正态分布的变量。但如果变量有极端异常值比如收入里有几个几百万的Z-Score会被拉偏标准化后的分布仍然有长尾。RobustScaler用的是中位数和四分位距对异常值更稳健。我的经验是收入、负债这类容易有极端值的变量用RobustScaler年龄、逾期次数这类分布相对均匀的用Z-Score。如果懒得区分统一用RobustScaler也不会出大错。代码上很简单from sklearn.preprocessing import RobustScaler, StandardScaler robust_cols [monthly_income, dti, loan_amount] standard_cols [age, delinquency_count, credit_history_months] scaler_robust RobustScaler() scaler_standard StandardScaler() df[robust_cols] scaler_robust.fit_transform(df[robust_cols]) df[standard_cols] scaler_standard.fit_transform(df[standard_cols])3.3 类别变量的编码陷阱前面提了LabelEncoder的问题这里展开说。One-Hot编码是 safest 的选择但如果类别数很多比如职业类型有50种One-Hot后维度会爆炸。这时候可以用目标编码Target Encoding用该类别的坏样本率来替换类别值。但目标编码容易过拟合必须配合交叉验证或者平滑处理。还有一个折中方案把高频类别保留低频类别合并成“其他”。比如职业类型里前10种占了90%的样本剩下的40种合并成“其他”。这样既控制了维度又保留了主要信息。3.4 降维PCA不是唯一选择PCA是最常用的降维方法但它假设主成分是原始特征的线性组合解释性差。对于信贷场景业务方往往想知道“这个客户为什么被分到高风险群”PCA的主成分很难回答这个问题。替代方案有特征重要性筛选用随机森林或XGBoost跑一遍预警模型取重要性前20的特征。方差膨胀因子VIF筛选剔除共线性高的特征。UMAP或t-SNE用于可视化降维但不适合作为聚类输入因为它们是非线性变换距离含义会变。我通常的做法是先用VIF剔除共线性特征再用随机森林重要性筛选到20-30个特征最后如果维度还是高再用PCA降到10维左右。这样既控制了维度又保留了一定的解释性。4. 聚类算法选型K-Means、GMM还是层次聚类4.1 三种聚类算法的本质差异热词里出现了“层次聚类python”“sklearn 层次聚类”“k值聚类”“高斯混合模型gmm”说明大家在这个环节纠结得最多。我把这三种算法的核心差异列一下算法核心假设优点缺点适用场景K-Means簇是球形方差相近快、简单、可扩展对初始点敏感需指定K对非球形簇效果差样本量大、簇形状规则GMM数据由多个高斯分布混合生成软聚类给出概率可处理椭圆形簇计算慢对初始化敏感可能收敛到局部最优簇有重叠、需要概率输出层次聚类簇有层次结构不需要指定K可可视化树状图计算复杂度高O(n^2)不适合大样本小样本、需要探索簇结构对于互联网贷款客户聚类我一般推荐GMM。原因是客户群体之间的边界往往不是硬性的一个客户可能同时具有“高收入但高负债”和“低收入但低负债”的特征硬聚类会强行把他分到一边而GMM可以给出“70%属于A群30%属于B群”的概率输出这对后续的风险定价更有参考价值。4.2 K值怎么定肘部法、轮廓系数和业务判断K-Means和GMM都需要指定簇数。肘部法看的是簇内平方和inertia随K变化的拐点轮廓系数看的是簇内紧密度和簇间分离度的综合指标。但这两个指标都只是参考最终K值要结合业务判断。比如你跑出来K3时轮廓系数最高但业务上你希望把客户分成“优质、次优、关注、高风险”四档那K4更合理。我通常会跑K2到K10把肘部图和轮廓系数图画出来然后选一个指标不太差、业务上能解释的K值。from sklearn.mixture import GaussianMixture from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt k_range range(2, 11) bic_scores [] sil_scores [] for k in k_range: gmm GaussianMixture(n_componentsk, random_state42, n_init5) labels gmm.fit_predict(X_pca) bic_scores.append(gmm.bic(X_pca)) sil_scores.append(silhouette_score(X_pca, labels)) fig, ax1 plt.subplots() ax1.plot(k_range, bic_scores, b-o, labelBIC) ax1.set_xlabel(K) ax1.set_ylabel(BIC, colorb) ax2 ax1.twinx() ax2.plot(k_range, sil_scores, r-s, labelSilhouette) ax2.set_ylabel(Silhouette, colorr) plt.show()GMM有个好处是可以用BIC贝叶斯信息准则来选KBIC越小越好。但BIC倾向于选简单的模型有时候会低估K值所以还是要结合轮廓系数和业务判断。4.3 聚类结果的业务解读聚类跑完不是终点关键是怎么解读每个群的特征。我一般会做两件事一是计算每个群在各个特征上的均值和全局均值对比找出这个群的“标签”二是看每个群的坏样本率逾期率这直接决定了风险等级。举个例子假设K4跑出来四个群群0平均年龄35岁月收入1.5万DTI 0.3逾期率2%。标签优质客户。群1平均年龄28岁月收入8000DTI 0.5逾期率8%。标签年轻成长型中等风险。群2平均年龄42岁月收入2万DTI 0.6逾期率15%。标签高收入高负债关注类。群3平均年龄30岁月收入5000DTI 0.8逾期率35%。标签高风险。这样分完预警名单就是群2和群3定价上群0给最低利率群3给最高利率或者直接拒贷。整个逻辑就通了。5. 风险预警建模从聚类标签到可解释的预警规则5.1 预警模型选型XGBoost还是逻辑回归聚类分群之后风险预警可以有两种做法一种是把聚类标签作为特征训练一个监督模型另一种是直接用原始特征训练预警模型聚类结果只用于分群展示。我倾向于两者结合用聚类标签作为特征之一同时保留原始特征这样模型既能利用分群信息又能捕捉群内的细微差异。模型选型上XGBoost和LightGBM是首选因为信贷数据往往有非线性关系和高维稀疏特征树模型天然适合。但逻辑回归也有优势系数可解释能直接输出概率方便做评分卡。如果答辩时评委问“为什么这个客户被预警了”逻辑回归可以给出“因为他的DTI系数是正的且值很高”XGBoost只能给特征重要性解释性差一些。我的建议是主模型用XGBoost同时训练一个逻辑回归作为可解释性补充。如果时间紧只做XGBoost也够用但一定要用SHAP值做解释。5.2 样本不平衡的处理信贷预警的坏样本率通常很低Lending Club数据里逾期率大概在15%-20%但真实互联网贷款可能只有5%甚至更低。样本不平衡会导致模型偏向多数类坏样本的召回率很低。处理方法有过采样SMOTE、ADASYN合成少数类样本。优点是简单缺点是可能引入噪声。欠采样随机删除多数类样本。优点是快缺点是浪费数据。调整类别权重在XGBoost里设置scale_pos_weight在逻辑回归里设置class_weightbalanced。阈值调整不改变模型只改变预测概率的截断阈值。我一般先用类别权重如果效果不够再用SMOTE。注意SMOTE只能在训练集上做不能对测试集做否则评估结果会虚高。from xgboost import XGBClassifier from sklearn.model_selection import train_test_split from imblearn.over_sampling import SMOTE X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.3, random_state42, stratifyy) # 先试类别权重 scale_pos_weight (y_train 0).sum() / (y_train 1).sum() model XGBClassifier(scale_pos_weightscale_pos_weight, random_state42) model.fit(X_train, y_train) # 如果效果不够再用SMOTE smote SMOTE(random_state42) X_train_res, y_train_res smote.fit_resample(X_train, y_train) model_smote XGBClassifier(random_state42) model_smote.fit(X_train_res, y_train_res)5.3 评估指标AUC不是唯一标准AUC是信贷预警最常用的指标但它有个问题它衡量的是排序能力不关心具体的概率校准。在实际业务里我们往往需要一个准确的概率值来做定价这时候AUC就不够了。补充指标有KS值衡量模型区分好坏样本的能力KS0.3通常认为可用。PR曲线在样本极度不平衡时PR曲线比ROC曲线更能反映模型对少数类的识别能力。概率校准用Platt Scaling或Isotonic Regression校准概率让预测概率更接近真实逾期率。我一般会同时看AUC、KS和PR-AUC如果三个指标都还行模型基本可用。如果AUC高但KS低说明模型排序能力有但区分度不够可能需要调整特征或模型。5.4 预警规则的可解释化模型跑完业务方最关心的是“哪些客户会被预警为什么”。SHAP值是当前最流行的解释工具它可以给出每个特征对每个样本预测值的贡献。对于信贷场景我一般会输出两类解释全局解释哪些特征对预警最重要比如DTI、多头借贷指数、逾期次数。局部解释某个具体客户为什么被预警比如“他的DTI0.8贡献了0.3的预警分数”。import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test) # 全局特征重要性 shap.summary_plot(shap_values, X_test, plot_typebar) # 单个样本解释 shap.force_plot(explainer.expected_value, shap_values[0,:], X_test.iloc[0,:])这些图可以直接放到可视化大屏上答辩时非常加分。6. 可视化落地从ECharts大屏到风险定价展示6.1 可视化大屏的整体布局热词里“百度可视化大屏”“企业级数据可视化”“ECharts数据可视化”反复出现说明大家对这个项目的可视化期待很高。我建议大屏至少包含这几个模块顶部指标卡总客户数、预警客户数、平均逾期率、平均利率。左侧聚类分布用饼图或环形图展示各群的客户占比。中间风险地图用热力图或散点图展示客户在“收入-DTI”平面上的分布颜色代表风险等级。右侧预警列表滚动展示高风险客户ID和预警原因。底部趋势图展示近12个月的逾期率变化和利率变化。ECharts是实现这些图表的最佳选择因为它免费、文档全、社区活跃。如果要做大屏适配可以用rem做单位配合flex布局保证在不同分辨率下不变形。6.2 聚类结果的可视化降维到二维再画聚类是在高维空间做的但可视化只能画二维或三维。所以需要先用PCA或t-SNE把数据降到二维再画散点图颜色代表聚类标签。注意PCA降维后的坐标轴没有业务含义不要标成“收入”和“年龄”直接标“PC1”和“PC2”就行。from sklearn.decomposition import PCA import plotly.express as px pca PCA(n_components2) X_2d pca.fit_transform(X_pca) fig px.scatter(xX_2d[:,0], yX_2d[:,1], colorlabels.astype(str), title客户聚类分布PCA降维, labels{x: PC1, y: PC2, color: Cluster}) fig.show()如果要用ECharts可以把X_2d和labels导出成JSON前端用ECharts的scatter图渲染。这样前后端分离Python只负责算前端只负责画。6.3 风险定价的可视化利率-风险等级映射风险定价的可视化核心是展示“不同风险等级的客户对应什么利率区间”。可以用箱线图或者小提琴图横轴是风险等级纵轴是利率。这样一眼就能看出高风险群的利率明显高于低风险群。如果要做更细的展示可以画一个“风险分数-利率”的散点图每个点代表一个客户颜色代表实际逾期状态。这样既能展示定价策略又能验证定价是否合理——如果高风险客户拿了低利率说明定价模型有问题。6.4 前后端联调中的常见坑做可视化大屏最容易踩的坑是数据格式不匹配。Python导出的JSON里numpy的int64和float64不能直接序列化需要转成Python原生类型。解决办法是用json.dumps时指定defaultstr或者在导出前用.astype(int)和.astype(float)转换。另一个坑是跨域请求。如果前端和后端分开部署浏览器会拦截跨域请求。解决办法是在后端加CORS头或者用Nginx做反向代理。如果只是本地演示直接用Flask的flask-cors库就行。from flask import Flask, jsonify from flask_cors import CORS app Flask(__name__) CORS(app) app.route(/api/cluster_data) def cluster_data(): data {clusters: labels.tolist(), pca_x: X_2d[:,0].tolist(), pca_y: X_2d[:,1].tolist()} return jsonify(data)7. 答辩前必须检查的几件事7.1 代码能不能一键跑通毕设答辩最尴尬的场景是老师让你现场跑一下代码结果报错。所以答辩前一定要在一个干净的环境里从头跑一遍确保requirements.txt里的依赖版本正确数据文件路径没有硬编码随机种子固定。我一般会写一个run_all.py把数据加载、特征工程、聚类、预警、可视化导出串起来一键执行。7.2 业务逻辑能不能自圆其说评委老师不一定会问算法细节但一定会问“你这个东西有什么用”。所以你要准备好一套业务话术聚类把客户分成几群每群的特征是什么预警模型抓出了哪些高风险客户定价策略怎么定。最好能举一个具体客户的例子从原始数据到最终预警和定价完整走一遍。7.3 可视化大屏能不能离线演示答辩现场的网络不一定稳定如果大屏依赖CDN加载ECharts可能会加载失败。解决办法是把ECharts的JS文件下载到本地用相对路径引用。数据也最好提前导出成JSON文件不要现场调API。7.4 论文和代码的一致性很多同学代码跑的是A方案论文写的是B方案答辩时被老师发现就麻烦了。所以定稿前一定要核对论文里的公式、参数、流程图和代码里的实现是否一致。如果不一致要么改代码要么改论文不能两边各说各话。8. 我在这个项目里踩过的几个坑第一个坑是聚类前忘了做标准化结果收入变量主导了距离计算聚类结果完全按收入分层其他特征全被淹没。后来加了RobustScaler才正常。这个坑很隐蔽因为代码不会报错只是结果不对如果不做业务解读很难发现。第二个坑是GMM的初始化。GMM对初始均值向量很敏感默认的kmeans初始化有时候会收敛到局部最优。解决办法是设置n_init10多跑几次取BIC最小的。这个参数在sklearn的GaussianMixture里默认是1很多人不知道要改。第三个坑是可视化大屏的适配。我用的是1920x1080的设计稿结果答辩教室的投影是1024x768大屏直接溢出右侧的预警列表完全看不到。后来改成用百分比布局才勉强适配。建议一开始就用响应式布局别等到答辩前才发现。第四个坑是SHAP值的计算速度。XGBoost模型跑SHAP值如果样本量上万计算会很慢。解决办法是用shap.sample()抽样计算或者用TreeExplainer的feature_perturbationinterventional模式速度会快很多。第五个坑是论文查重。信贷风控这个方向很多术语和公式是通用的比如AUC的定义、KS的计算公式这些很容易重复。我的经验是公式部分用MathType重新打一遍不要直接复制业务描述部分用自己的话重新组织不要照搬参考文献。9. 这个项目还能怎么扩展如果你时间充裕有几个方向可以继续挖。一是引入时间维度做动态聚类看客户群随时间怎么迁移。二是把风险定价做成一个优化问题在利润最大化和风险最小化之间找平衡。三是接入实时数据流用Kafka或Flume做在线预警。四是把模型部署成API用FastAPI或Flask封装前端直接调用。不过对于毕设来说把当前这套流程跑通、跑稳、讲清楚已经足够了。贪多嚼不烂答辩时老师更看重的是你对每个环节的理解深度而不是堆了多少技术名词。