简介:这份PDF文档面向金融风控从业者、算法工程师及深度学习入门者,系统讲解如何用PyTorch构建并优化企业信用评分卡模型。内容从金融风控与评分卡概述切入,依次覆盖PyTorch环境搭建、财务与经营等多源数据预处理、逻辑回归/决策树/神经网络三类模型的构建与评估,并深入特征工程、模型融合、超参数调优、正则化与早停等优化策略,最后延伸至本地、云平台与容器化部署及信贷审批、风险预警等应用案例。资源包为1个PDF文件,大小约2.12MB,支持目录章节跳转与阅读器左侧大纲快速定位,31页内容完整、图表清晰。已有107人学习下载。读者可借此掌握从数据准备、模型训练到评估部署的完整链路,理解准确率、精确率、召回率、F1值与AUC等指标的解读方法,并获得可复用的评分卡建模与调优思路。
1. 从一份 31 页的 PDF 说起:PyTorch 做企业信用评分卡到底靠不靠谱
很多做金融风控的同行,第一次接触信用评分卡,脑子里蹦出来的还是 SAS、SPSS 或者 sklearn 里那套LogisticRegression。逻辑回归加 WOE 分箱,这套组合拳打了十几年,稳是真稳,但遇到高维稀疏特征、非线性交互、多源异构数据的时候,线性模型的表达能力就开始捉襟见肘。这份 31 页的文档《金融风控核心算法:PyTorch 实现企业信用评分卡模型的构建与优化》,走的是另一条路——用 PyTorch 把评分卡从传统统计模型拉到深度学习框架里来做,同时保留评分卡本身对可解释性和业务落地的要求。
它解决的核心问题是:怎么在 PyTorch 里从零搭出一个能跑通企业信用评分卡全流程的模型,包括数据准备、逻辑回归/决策树/MLP 三种模型的构建、AUC 等指标评估、超参数调优和正则化防过拟合,最后还落到部署和应用场景。适合谁看?一是已经会用 sklearn 做评分卡、想往深度学习方向迁移的风控建模工程师;二是刚入行、需要一份能照着敲代码跑通全流程的实战参考的新人;三是需要向团队论证「PyTorch 做评分卡不是炫技」的技术负责人。文档支持目录跳转和左侧大纲定位,31 页翻起来不费劲,但真正值钱的是里面那些代码块和参数设置——下面我按实际复现的顺序,把这份文档拆开讲。
2. 环境搭建与数据准备:PyTorch 装完之后第一件事做什么
2.1 PyTorch 环境搭建的版本对应关系
文档里给的安装步骤不复杂:装 Python 3.9+、创建虚拟环境、pip install torch torchvision torchaudio。但这里有个血泪经验——Python 和 PyTorch 的版本对应关系如果搞错,后面import torch直接报DLL load failed或者undefined symbol,排查起来非常费时间。我一般会先确认三件事:Python 版本、CUDA 版本(如果用 GPU)、PyTorch 版本。CPU 版本无所谓,GPU 版本必须严格对齐。
# 创建虚拟环境(venv 方式,文档里也提了 conda) python -m venv risk_env source risk_env/bin/activate # Linux/Mac # risk_env\Scripts\activate # Windows # 安装 CPU 版本 PyTorch(最稳妥的起步方式) pip install torch torchvision torchaudio # 验证安装 python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"逻辑说明:先建虚拟环境是为了避免和系统里其他项目的依赖打架,尤其是 numpy、pandas 这些风控常用库的版本冲突。torch.cuda.is_available()返回False不代表装错了,CPU 版本本来就是 False。如果你有 NVIDIA 显卡并且装了 CUDA,去 PyTorch 官网选对应 CUDA 版本的安装命令,不要直接pip install torch了事。
参数说明:python -m venv后面的risk_env是环境名,随便起,但建议和项目名挂钩。激活命令在 Windows 和 Linux/Mac 下不一样,文档里写得很清楚,照抄就行。conda 用户用conda create -n risk_env python=3.9也可以,但 conda 装 PyTorch 有时候会走 conda 源,版本更新慢,我一般还是用 pip 装。
2.2 企业信用评分卡的数据收集与预处理
文档把数据来源分成四类:财务数据(资产负债表、利润表、现金流量表)、经营数据(销售额、市场份额、客户满意度)、信用记录数据(贷款记录、还款记录、违约记录)、外部环境数据(宏观经济、行业数据、政策法规)。这个分类是标准的评分卡数据框架,但实际做项目的时候,财务数据和信用记录数据是主力,经营数据和外部环境数据更多是补充。数据来源方面,文档提到 Wind、同花顺、央行征信系统、第三方信用评级机构,这些渠道的数据获取有门槛,学习阶段可以用公开数据集或者模拟数据替代。
预处理部分文档给了四个步骤:数据清洗、数据编码、数据标准化、数据划分。代码示例用的是 pandas 和 sklearn,这部分和传统评分卡流程完全一致,可以直接复用。
import pandas as pd import numpy as np from sklearn.preprocessing import OneHotEncoder, StandardScaler from sklearn.model_selection import train_test_split # 1. 缺失值处理:均值填充(文档示例) data = pd.DataFrame({'col1': [1, np.nan, 3], 'col2': [4, 5, np.nan]}) data.fillna(data.mean(), inplace=True) # 2. 异常值处理:Z-score 法,阈值设为 3 from scipy import stats z_scores = np.abs(stats.zscore(data.select_dtypes(include=[np.number]))) filtered_data = data[(z_scores < 3).all(axis=1)] # 3. 分类变量编码:独热编码 encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore') encoded = encoder.fit_transform(data[['category_col']]) # 4. 数值标准化:Z-score 标准化 scaler = StandardScaler() scaled = scaler.fit_transform(data.select_dtypes(include=[np.number])) # 5. 数据划分:80/10/10 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) X_train, X_val, y_train, y_val = train_test_split( X_train, y_train, test_size=0.125, random_state=42 )逻辑说明:缺失值用均值填充是最简单的做法,但风控数据里缺失值本身可能携带信息(比如某企业从不披露某项财务指标),更稳妥的做法是加一个缺失指示列。异常值用 Z-score 过滤,阈值 3 是经验值,但金融数据厚尾严重,Z-score 可能会误杀正常的大额交易,建议结合箱线图 IQR 法交叉验证。独热编码在类别数多的时候会导致维度爆炸,实际项目中我一般先用 WOE 编码或者目标编码,独热编码留给类别数少于 10 的特征。数据划分比例 80/10/10 是文档给的,但样本量小的时候(比如几千条),70/15/15 更稳,验证集和测试集各留 15% 能降低评估方差。
参数说明:random_state=42是固定随机种子,保证每次划分结果一致,方便复现。handle_unknown='ignore'是防止测试集出现训练集没见过的类别时报错。sparse_output=False在新版 sklearn 里替代了原来的sparse=False,如果你装的 sklearn 版本较老,改回sparse=False。
提示:数据预处理流程一定要固化成一个 pipeline 或者函数,训练时怎么处理,推理时就必须怎么处理。我见过太多模型离线 AUC 0.85、上线后效果腰斩的案例,根因就是推理时的标准化参数和训练时不一致。
3. 三种模型构建:逻辑回归、决策树、MLP 在 PyTorch 里怎么写
3.1 逻辑回归模型:PyTorch 版和 sklearn 版的本质区别
文档用 PyTorch 的nn.Module重新实现了逻辑回归,结构很简单:一个nn.Linear(input_size, 1)加一个nn.Sigmoid()。这和 sklearn 的LogisticRegression在数学上完全等价,但 PyTorch 版的好处是你可以自由修改损失函数、优化器、加正则化项,甚至把线性层换成更复杂的结构。
import torch import torch.nn as nn class LogisticRegression(nn.Module): def __init__(self, input_size): super(LogisticRegression, self).__init__() self.linear = nn.Linear(input_size, 1) self.sigmoid = nn.Sigmoid() def forward(self, x): out = self.linear(x) out = self.sigmoid(out) return out # 初始化 input_size = X_train.shape[1] model = LogisticRegression(input_size) criterion = nn.BCELoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 训练 num_epochs = 100 for epoch in range(num_epochs): inputs = torch.tensor(X_train, dtype=torch.float32) labels = torch.tensor(y_train, dtype=torch.float32).view(-1, 1) outputs = model(inputs) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() if (epoch+1) % 10 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')逻辑说明:nn.BCELoss()是二元交叉熵损失,要求输入已经过 Sigmoid 激活,所以模型forward里最后一步是sigmoid。如果你用nn.BCEWithLogitsLoss(),模型输出就不要加 Sigmoid,数值稳定性更好,推荐后者。optimizer.zero_grad()必须在loss.backward()之前调用,否则梯度会累加。labels.view(-1, 1)是把标签从(N,)变成(N, 1),和模型输出维度对齐。
参数说明:lr=0.01是 SGD 的学习率,逻辑回归用这个值一般没问题,但如果你换成 Adam,学习率要降到 0.001 左右。num_epochs=100是文档给的,实际训练时建议加早停,验证集损失连续 10 个 epoch 不下降就停。input_size是特征维度,独热编码后维度会膨胀,注意检查。
3.2 决策树模型:为什么用 sklearn 而不是 PyTorch
文档在决策树部分直接用了 sklearn 的DecisionTreeClassifier,没有用 PyTorch 实现。这个选择是对的——PyTorch 原生不支持决策树,硬要用神经网络模拟决策树结构,代码复杂且没必要。评分卡场景下,决策树更多是作为基线模型或者特征筛选工具,sklearn 的max_depth、min_samples_split等参数已经足够。
from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score dt_model = DecisionTreeClassifier(max_depth=5, min_samples_split=10, random_state=42) dt_model.fit(X_train, y_train) y_pred = dt_model.predict(X_test) accuracy = accuracy_score(y_test, y_pred) print(f'Accuracy: {accuracy:.4f}')逻辑说明:max_depth=5是文档设的,控制树深度防止过拟合。min_samples_split=10是我加的,表示节点样本数少于 10 就不分裂,进一步抑制过拟合。决策树在评分卡里的主要价值是可解释性——你可以直接把树结构画出来,跟业务方解释「为什么这个企业被拒」。
参数说明:max_depth越大模型越复杂,训练集准确率越高但测试集可能下降。min_samples_split和min_samples_leaf是控制过拟合的关键参数,金融数据噪声大,这两个值建议设大一点。random_state固定后结果可复现。
3.3 MLP 模型:PyTorch 做评分卡的真正优势所在
多层感知机是文档里唯一一个用 PyTorch 完整实现的非线性模型。结构是:输入层 → 全连接层 → ReLU → 全连接层 → Sigmoid。隐藏层神经元数量设为 10,输出维度 1。
class MLP(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(MLP, self).__init__() self.fc1 = nn.Linear(input_size, hidden_size) self.relu = nn.ReLU() self.fc2 = nn.Linear(hidden_size, output_size) self.sigmoid = nn.Sigmoid() def forward(self, x): out = self.fc1(x) out = self.relu(out) out = self.fc2(out) out = self.sigmoid(out) return out input_size = X_train.shape[1] hidden_size = 10 output_size = 1 model = MLP(input_size, hidden_size, output_size) criterion = nn.BCELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) num_epochs = 200 for epoch in range(num_epochs): inputs = torch.tensor(X_train, dtype=torch.float32) labels = torch.tensor(y_train, dtype=torch.float32).view(-1, 1) outputs = model(inputs) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() if (epoch+1) % 20 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')逻辑说明:MLP 比逻辑回归多了一个隐藏层和 ReLU 激活,能捕捉特征间的非线性交互。hidden_size=10是文档设的,实际项目中这个值需要调,太小欠拟合,太大过拟合。优化器从 SGD 换成了 Adam,学习率降到 0.001,这是深度学习模型的标准配置。
参数说明:hidden_size是隐藏层神经元数量,经验值是输入维度的 1/2 到 2/3,但不要超过 128,否则小样本数据必然过拟合。num_epochs=200配合 Adam 一般够用,但还是要看验证集损失曲线。lr=0.001是 Adam 的常用学习率,如果损失震荡,降到 0.0005。
注意:MLP 在评分卡场景下最大的问题是可解释性差。监管要求「模型决策可解释」的时候,MLP 很难像逻辑回归那样给出每个特征的权重。折中方案是用 SHAP 值做事后解释,或者用 MLP 做特征交叉,最后再套一个逻辑回归层。
4. 模型评估与指标解读:AUC 高不代表模型能用
4.1 准确率、精确率、召回率、F1 的计算与业务含义
文档把评估指标讲得很细,准确率、精确率、召回率、F1、ROC/AUC 都给了公式和 sklearn 代码。这里不重复公式,重点说业务含义。准确率在样本不平衡时基本没用——如果 95% 的企业都是好企业,模型全预测「好」,准确率也有 95%,但坏企业一个没抓出来。精确率关注「预测为坏的里面有多少真坏」,召回率关注「真坏的里面有多少被预测出来」。评分卡场景下,召回率通常比精确率重要,因为漏掉一个坏企业的损失远大于误拒一个好企业。
from sklearn.metrics import (accuracy_score, precision_score, recall_score, f1_score, roc_curve, auc, confusion_matrix) import matplotlib.pyplot as plt # 假设 y_true 是真实标签,y_score 是模型输出的概率 y_pred = (y_score >= 0.5).astype(int) print(f"Accuracy: {accuracy_score(y_true, y_pred):.4f}") print(f"Precision: {precision_score(y_true, y_pred):.4f}") print(f"Recall: {recall_score(y_true, y_pred):.4f}") print(f"F1: {f1_score(y_true, y_pred):.4f}") # ROC 曲线和 AUC fpr, tpr, thresholds = roc_curve(y_true, y_score) roc_auc = auc(fpr, tpr) plt.plot(fpr, tpr, label=f'ROC curve (area = {roc_auc:.2f})') plt.plot([0, 1], [0, 1], 'k--') plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.legend(loc="lower right") plt.show() # 混淆矩阵 cm = confusion_matrix(y_true, y_pred) print(cm)逻辑说明:y_pred是用 0.5 阈值把概率转成类别,但评分卡场景下阈值不一定是 0.5,要根据业务对误拒率和误授率的容忍度来调。ROC 曲线不受阈值影响,AUC 衡量的是模型在所有阈值下的排序能力。混淆矩阵直接看 TP、FP、FN、TN 的数量,比单个指标更直观。
参数说明:roc_curve返回的thresholds可以帮你找到最佳阈值——比如在召回率不低于 0.8 的前提下,选精确率最高的那个阈值。auc值 0.5 是随机猜测,0.7 以上算可用,0.8 以上算不错,但金融风控里 0.75 和 0.78 的差距可能对应几千万的坏账,不能只看绝对值。
4.2 三种模型的评估对比与选择逻辑
文档在 4.4 节对比了逻辑回归、决策树、神经网络三种模型的评估指标。实际跑下来,逻辑回归的 AUC 通常最低但最稳定,决策树容易过拟合导致测试集 AUC 波动大,MLP 在特征工程到位的情况下 AUC 最高但调参成本也最高。选择逻辑不是「哪个 AUC 高选哪个」,而是看业务需求:如果监管要求强解释性,逻辑回归加 WOE 是唯一选择;如果追求排序能力且能接受黑箱,MLP 可以上;决策树更多是作为特征筛选和规则提取的中间工具。
| 模型 | 可解释性 | 训练速度 | 过拟合风险 | 典型 AUC 范围 |
|---|---|---|---|---|
| 逻辑回归 | 强 | 快 | 低 | 0.70-0.78 |
| 决策树 | 中 | 快 | 高 | 0.65-0.75 |
| MLP | 弱 | 中 | 中高 | 0.75-0.85 |
提示:AUC 高不代表模型上线后效果好。我见过离线 AUC 0.82 的模型,上线后因为特征分布漂移,实际效果还不如 AUC 0.75 的简单模型。评估阶段一定要做时间外样本验证,不能用随机划分的测试集。
5. 避坑与排查:评分卡模型从训练到上线最容易翻车的五个地方
5.1 现象:训练集损失正常下降,验证集损失从第 20 个 epoch 开始上升
原因:过拟合。MLP 参数量相对样本量太大,或者训练轮数过多。评分卡数据通常只有几千到几万条,MLP 隐藏层超过 64 个神经元就很容易过拟合。
解决:加早停(验证集损失连续 10 个 epoch 不下降就停)、加 L2 正则化(optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4))、减小隐藏层维度。文档 5.4 节提到的早停策略和正则化就是针对这个问题。
5.2 现象:独热编码后特征维度从 50 涨到 2000,训练极慢且 AUC 不升反降
原因:高基数分类变量(比如企业所属行业细分、注册地区)做独热编码导致维度爆炸,稀疏特征让线性模型和 MLP 都难以有效学习。
解决:改用 WOE 编码或目标编码,把高基数类别变量压缩成单列数值特征。WOE 编码还能保留单调性,对评分卡特别友好。如果一定要用独热编码,先做类别合并,把低频类别归到「其他」。
5.3 现象:模型在测试集上 AUC 0.80,上线后业务反馈「坏企业一个没抓到」
原因:测试集和线上数据的标签定义不一致,或者测试集划分时用了未来数据。比如用 2024 年全年的数据随机划分,训练集里混入了 2024 年 12 月的数据,而测试集是 2024 年 1 月的数据,时间穿越导致评估虚高。
解决:按时间划分训练集和测试集,训练集用早期数据,测试集用后期数据。评分卡模型必须做时间外验证(OOT),随机划分的评估结果只能参考。
5.4 现象:PyTorch 训练时 loss 变成 NaN
原因:学习率太大、数据未标准化、或者 BCELoss 输入了未经过 Sigmoid 的值。nn.BCELoss()要求输入在 0 到 1 之间,如果模型输出没加 Sigmoid,或者加了但数值溢出,loss 就会 NaN。
解决:检查模型forward最后是否有 Sigmoid;改用nn.BCEWithLogitsLoss()(内部自带 Sigmoid,数值更稳定);降低学习率;确认输入数据已经标准化。
5.5 现象:同一份代码,别人跑 AUC 0.78,我跑只有 0.65
原因:随机种子没固定、数据划分方式不同、或者环境里 PyTorch 版本不一致导致初始化权重不同。
解决:固定所有随机种子——torch.manual_seed(42)、np.random.seed(42)、random.seed(42)。数据划分用同一个random_state。确认 PyTorch 版本一致,不同版本的默认权重初始化策略可能有差异。
6. 进阶技巧:用早停和权重衰减把 MLP 的泛化能力再提一档
文档 5.4 节讲了正则化和早停,但代码层面没有给出完整实现。这里补一个我实际项目中常用的训练循环,把早停、权重衰减、学习率调度串在一起。核心思路是:每训练一个 epoch,就在验证集上算一次损失和 AUC,如果验证集 AUC 连续 15 个 epoch 没有提升,就停止训练并回滚到最佳模型参数。
import copy import torch import torch.nn as nn import numpy as np from sklearn.metrics import roc_auc_score # 模型、损失、优化器(带权重衰减) model = MLP(input_size, hidden_size=32, output_size=1) criterion = nn.BCEWithLogitsLoss() # 更稳定的损失函数 optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, mode='max', patience=5, factor=0.5 ) # 数据转张量 X_train_t = torch.tensor(X_train, dtype=torch.float32) y_train_t = torch.tensor(y_train, dtype=torch.float32).view(-1, 1) X_val_t = torch.tensor(X_val, dtype=torch.float32) y_val_t = torch.tensor(y_val, dtype=torch.float32).view(-1, 1) best_auc = 0.0 best_model_state = None patience_counter = 0 max_patience = 15 for epoch in range(300): model.train() optimizer.zero_grad() outputs = model(X_train_t) loss = criterion(outputs, y_train_t) loss.backward() optimizer.step() # 验证集评估 model.eval() with torch.no_grad(): val_outputs = model(X_val_t) val_prob = torch.sigmoid(val_outputs).numpy().flatten() val_auc = roc_auc_score(y_val, val_prob) scheduler.step(val_auc) if val_auc > best_auc: best_auc = val_auc best_model_state = copy.deepcopy(model.state_dict()) patience_counter = 0 else: patience_counter += 1 if patience_counter >= max_patience: print(f'Early stopping at epoch {epoch+1}, best AUC: {best_auc:.4f}') break if (epoch+1) % 20 == 0: print(f'Epoch [{epoch+1}], Loss: {loss.item():.4f}, Val AUC: {val_auc:.4f}') # 回滚到最佳模型 model.load_state_dict(best_model_state)逻辑说明:BCEWithLogitsLoss把 Sigmoid 和 BCELoss 合并在一起,数值稳定性更好,模型forward里就不需要加 Sigmoid 了。weight_decay=1e-4是 L2 正则化,抑制权重过大。ReduceLROnPlateau在验证集 AUC 连续 5 个 epoch 不提升时把学习率减半,帮助模型跳出局部最优。早停的max_patience=15比学习率调度的patience=5大,是为了给学习率衰减留出调整空间。copy.deepcopy保存最佳模型参数,训练结束后回滚,避免最终模型是过拟合状态。
参数说明:hidden_size=32比文档里的 10 大,是因为加了正则化和早停后,模型容量可以适当放大。weight_decay从 1e-5 到 1e-3 之间调,数据量越小这个值应该越大。max_patience设 15 是经验值,样本量少于 5000 时降到 10,大于 50000 时可以放到 20。scheduler的mode='max'是因为监控的是 AUC,越大越好;如果监控 loss,改成mode='min'。
从那以后我每次训练评分卡模型,都强制走一遍「固定随机种子 → 时间外样本划分 → 早停 + 权重衰减 → 验证集 AUC 回滚」这套流程,再也没出现过离线评估和上线效果严重脱节的情况。希望帮到你。
本文还有配套的精品资源,点击获取