最近好几个做课程设计的同学跑来问我同一个问题:老师让实现一个“基于随机森林的贷款可能性预测系统”,听起来很高大上,但一打开项目文档就不知道该从哪下手。其实这个题目特别适合作为课程设计或者毕业设计,因为它既不是那种纯跑模型就完事的玩具Demo,也还没复杂到需要上深度学习框架的程度。它需要你真正把数据、算法、代码、界面、评估串起来,形成一个能演示、能答辩、能写进简历的完整系统。
说直白一点,“基于随机森林的贷款可能性预测系统”要做的就是一件事:收集借款人的历史数据,训练一个随机森林模型,然后当输入一个新客户的信息时,系统输出他“大概率会按时还款”还是“大概率会逾期”,并给出一个概率值。这个系统能解决的问题非常明确:信贷审批场景里的风险判断。适合参考这篇内容的人,包括正在做课程设计的大三学生、准备毕业设计的应届生,以及想快速上手一个完整机器学习项目的转行开发。下面我把整个项目从设计思路到代码实现,再到调参踩坑,完整拆开讲一遍。
1. 项目整体思路与系统设计
很多同学拿到这种题目第一反应是“我先去跑个随机森林模型”,这其实是最大的误区。建模只是整个系统的一小部分,课程设计和毕业设计考察的是你能否从一个业务问题出发,做出一套能用的东西。所以先别急着写代码,把项目整体架构想清楚,后面会顺利非常多。
1.1 一个贷款预测系统到底要解决什么问题
贷款可能性预测,放在真实业务里就是信贷审批环节的“风险评分”。银行或者金融科技公司收到一笔借款申请时,不会只看申请人的一句承诺,而是根据一系列可量化的指标来做判断:收入、负债、工作年限、历史还款记录、借款金额、借款用途等等。这些指标里既有数值型的,也有类别型的,还有大量缺失值。
随机森林在这个场景中的价值在于,它能自动处理这些混杂特征之间的复杂关系。举个例子,一个人的收入很高,但负债率同样很高,这时候单看任何一个字段都会得到片面结论。随机森林用大量决策树投票的方式,把多个维度的信号融合在一起,得到的结果比单棵决策树或者简单规则稳定得多。
从这个角度理解,这个系统的核心功能就非常清晰了:输入是客户特征,输出是还款概率或违约概率。系统除了模型本身,还需要具备数据管理、预测结果展示、批量测试这些辅助功能,否则就只是一个Jupyter Notebook,称不上“系统”。
1.2 为什么选随机森林而不是神经网络或逻辑回归
课程设计选随机森林,有的是老师指定的,有的是自己选的。但不管哪种情况,你都得能说出“为什么是它”的理由,这是答辩时最高频的问题。
逻辑回归是传统方案里最常见的,好处是解释性极强,每个特征的系数可以直接解释为“收入每增加一万元,违约概率变化多少”。但它的问题在于,信贷数据里特征之间往往存在非线性关系和交互作用,逻辑回归需要你手动构造大量交叉特征,这对课设阶段来说工作量太大,而且效果未必好。
神经网络理论上能力更强,能拟合非常复杂的关系,但它的训练需要更多数据、更多调参经验,对课设这个体量来说性价比很低。一个只有几千条样本的信贷数据集,你很难把神经网络训到比随机森林更好的水平,反而容易过拟合。
随机森林正好卡在中间。它不需要特别精细的特征工程,对缺失值和不平衡数据有一定容忍度,训练速度可控,还能输出特征重要性用于解释。最重要的是,它几乎不需要调参就能得到一个还不错的baseline,这对想在一个学期内完成项目的同学非常友好。我在下面的章节会详细展开它的原理,这里先记住结论:随机森林是“稳健性”和“可解释性”平衡得最好的课设选择。
1.3 系统功能模块怎么划分
一个合格的贷款可能性预测系统,至少应该包含四个模块,缺任何一个都不完整。
第一个是数据管理模块。负责加载数据集、预览数据分布、统计缺失值、处理异常值。别小看这部分,答辩时老师最喜欢从这里深入问起,因为数据质量决定模型上限。
第二个是模型训练模块。这也是随机森林算法的核心承载模块,需要支持对训练集和测试集的划分、模型训练、保存模型文件。这里有两个隐藏要求:训练要快,结果要可复现。
第三个是预测服务模块。用户通过界面录入或者上传批量数据后,系统调用已保存的模型进行预测,返回每个客户“可能按时还款”的概率,并给出“建议通过”或“建议拒绝”的结论。
第四个是结果可视化模块。包括预测结果分布、特征重要性排序、混淆矩阵、ROC曲线等。这些图表是答辩时的加分项,它证明了你不只是把代码跑通,而是真正理解了模型在做什么。
我建议你按照这四个模块来做项目管理,每个模块单独写代码文件,后面每个部分都能独立测试。不要试图一口气把所有代码堆在一个文件里,课设阶段最怕的就是出了问题没法定位。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据集选择与特征工程
很多课设做到一半卡壳,不是模型写不出来,而是数据没有处理好。一个干净、字段含义明确的数据集,能让整个项目顺畅很多。这一节我会聊聊如何选数据,以及怎么把原始字段变成随机森林真正能吃的格式。
2.1 数据集怎么选,我用的是哪一个
贷款预测领域有好几个公开数据集可以选。常见的有Kaggle上的“Give Me Some Credit”信贷预测数据集、UCI的德国信用数据集,以及Lending Club的贷款记录数据。
我个人的建议是优先选“Give Me Some Credit”或者德国信用数据集,原因是它们的字段数量适中,样本量几百到十几万都有,不会因为数据量太大导致训练时间失控。德国信用数据集只有1000条样本,非常适合快速跑通流程;而Give Me Some Credit有15万条样本,模型训练起来更有真实感,但数据清洗工作量也更大。
我当时用的是Kaggle上的Give Me Some Credit数据集,字段包括借款人年龄、年收入、债务比率、月度收入、逾期次数、信用卡使用率等。这些字段天然贴近“贷款可能性”这个主题,不用自己去编造业务含义。如果你所在的学校网络访问Kaggle不方便,也可以直接用sklearn里自带的或者从国内的一些公开数据集仓库下载。选择数据集的唯一硬性标准是:每条样本有特征字段,还有明确的标签列(是否违约),0代表正常还款,1代表违约。
2.2 特征处理:从原始字段到模型可用的输入
拿到数据之后,第一步不是建模,而是先做探索性分析。用pandas的df.info()和df.describe()看每个字段的数据类型、缺失情况、取值范围。信贷数据里最常见的特征问题是缺失值、极端异常值和类别编码。
先说缺失值。随机森林本身能够处理一定程度的缺失值,sklearn的实现里,树的节点分裂时会用代理分裂来处理缺失样本。但如果你直接把包含大量NaN的数据丢进去,仍然可能报错或者导致训练不稳定。稳妥的做法是手动填充。数值型字段,比如“月度收入”,一般用中位数填充,因为收入这类字段往往是右偏分布,用均值会被少数高收入人群拉高。类别型字段,用众数填充。
再说异常值。信贷数据里很容易出现“年龄 = 200”“年收入 = 99999999”这种明显不合理的记录。处理方式有两种,一种是直接删除,另一种是截断处理,比如把所有年龄超过100的样本统一替换为100。我建议用截断,因为删除样本在数据量本身就紧张的时候会很心疼。
最后是类别编码。随机森林虽然不像SVM或逻辑回归那样对数据尺度敏感,但类别型特征还是需要转换成数值。常见的做法是LabelEncoder或者OneHotEncoder。比如“住房情况”这个字段有“自有”“按揭”“租赁”三类,用LabelEncoder直接变成0、1、2就能用。有些人会担心直接把顺序编码会不会引入顺序关系,在随机森林里这个影响很小,因为树的每个分裂点都是独立的阈值判断,不是线性加权求和。
2.3 样本不均衡与标签处理
信贷违约数据集几乎都会面临样本不均衡问题:正常还款的人远比违约的人多。Give Me Some Credit数据集里违约比例只占6%左右。如果你直接在这个数据上训练模型,模型会“聪明”地学会把所有样本都预测为正常,因为这样准确率也有94%。
应对这个问题有两条路。第一条是不做任何重采样,但在评估时使用AUC、召回率这些对不平衡更敏感的指标。第二条是做SMOTE过采样或者对多数类进行下采样。课程设计阶段我建议两种方法都尝试一下,把结果对比放进报告里,这也是一个很好的亮点。实际操作时,只需要用imblearn.over_sampling.SMOTE就可以在训练集上生成合成样本,注意只能对训练集做,绝对不能在划分数据集之前做SMOTE,否则会数据泄漏,测试集不再可信。
3. 随机森林算法原理与选型理由
写代码之前,把随机森林的原理吃透,对你调试模型和答辩都有决定性帮助。这一节我会用尽量通俗的语言把决策树和随机森林讲清楚,同时解释几个关键机制背后的设计逻辑。
3.1 决策树和随机森林到底在做什么
随机森林的基座是决策树。决策树可以理解成一套“如果……那么……”的判断规则。比如判断一个人是否会违约,第一层可能问“债务比率是否大于0.5”,如果是,进入左边分支,再问“过去两年是否逾期超过2次”,如果是,就直接判定为高风险。这一层层划分下去,就形成了一棵从上到下的树。
决策树在训练时,会遍历每个特征、每个可能的阈值,选择一个能使划分后的样本“纯度”提升最多的分裂方式。纯度通常用基尼指数或信息熵来衡量。目标是让每个叶子节点里的样本标签尽可能一致,也就是尽量只包含正常还款的人或只包含违约的人。
但单棵决策树最大的问题是不稳定。训练样本稍微变一点点,树的形状就可能完全改变,而且它很容易过拟合,长得非常深,把所有训练数据都背下来。随机森林的出现就是为了解决这个问题。
随机森林的思路是“三个臭皮匠,顶个诸葛亮”。它同时训练几百棵决策树,每棵树都在不同的随机子集上训练,最终通过投票决定预测结果。这样即使某一棵树判断极端,整体也不会被带偏,方差被大幅降低。
3.2 随机森林的几个关键机制
随机森林的“随机”体现在两个地方。
第一个是样本随机。训练每一棵树时,并不是用全部训练数据,而是用有放回抽样抽出和原始数据同样大小的子集,这叫Bootstrap采样。这样每一棵树看到的数据都不完全一样。由于是有放回抽样,大约有三分之一的数据不会被某棵树抽到,这些没被抽到的数据叫做“袋外数据”,可以用来计算袋外误差,相当于免费的验证集。
第二个是特征随机。在每次节点分裂时,算法不是从所有特征里找最优分裂,而是只从随机选出的一个特征子集里找。这个子集大小通常是总特征数的平方根。这样做的目的是让每一棵树变得“各有所长”,比如某棵树着重用收入特征做判断,另一棵着重用逾期次数做判断,最终投票时这些长板互补,整体泛化能力更强。
还有一点值得在报告里写:随机森林可以输出特征重要性。实现原理也很直观,如果某个特征在树的众多分裂点中被频繁选择,而且去掉这个特征后模型的预测误差明显上升,那这个特征的重要性就高。你可以直接用model.feature_importances_拿到这个值,再结合业务常识判断模型学到的规则是否合理。
3.3 随机森林和其他模型的横向对比
为什么要对比?因为答辩时老师几乎一定会问“你的模型和别的模型比好在哪”。为了让你有话可说,我整理一个简洁的对比表:
| 模型 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 逻辑回归 | 可解释性强,训练快 | 需要大量特征工程,难处理非线性关系 | 银行传统评分卡 |
| 决策树 | 规则直观,无需缩放 | 容易过拟合,泛化性差 | 快速探索数据规则 |
| 随机森林 | 稳定,能处理非线性,抗过拟合,自带特征重要性 | 模型可解释性弱于逻辑回归,模型文件偏大 | 信贷审批、风控筛选 |
| XGBoost/LightGBM | 精度上限高,训练效率高 | 调参复杂,课程设计阶段容易浪费时间 | 提升精度的后续优化方向 |
| 神经网络 | 表达能力极强 | 需要大量数据和调参,解释性差 | 数据量极大的场景 |
如果你想让报告有层次,可以加一小节“模型对比实验”,用相同的数据分别训练逻辑回归、决策树和随机森林,比较AUC和召回率。这样随机森林的优势会非常直观,整个项目的技术深度也上来了。
4. 系统核心模块与完整实现
原理讲完,进入实操环节。这一节会给出完整的技术栈选择、模型训练代码、预测接口和前端交互的实现方案。所有代码都是我按照实际课设项目的常见路径整理的,可以直接作为参照。
4.1 技术栈选型
整个系统我用的是Python生态,原因很简单:课程设计阶段不需要引入Java或者C++来增加难度,Python的sklearn库封装好了随机森林,代码量最少,效果又能保证。
具体的技术栈如下:
- 数据处理:pandas、numpy
- 模型训练:scikit-learn,里面的
RandomForestClassifier就是随机森林分类器 - 数据可视化:matplotlib、seaborn,用来画ROC曲线、混淆矩阵、特征重要性柱状图
- 后端服务:Flask,轻量级Web框架,比Django更适合快速搭建
- 前端:一个简洁的HTML页面,配合JavaScript的fetch接口提交数据,直接展示预测结果
- 模型持久化:joblib或者pickle,把训练好的模型保存成文件
结构上建议这样组织目录:
code复制loan_prediction/
├── app.py # Flask应用入口,处理请求和页面跳转
├── train_model.py # 数据清洗、训练模型、保存模型文件
├── model.pkl # 训练得到的模型文件
├── templates/
│ └── index.html # 前端页面
├── static/
│ ├── style.css
│ └── script.js
└── data/
└── loan_data.csv # 原始数据集
4.2 模型训练模块实现
train_model.py是整个系统的核心之一,包含数据读取、预处理、训练和保存模型的过程。下面这段代码是可运行的核心框架:
python复制import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, roc_auc_score, confusion_matrix
import joblib
def load_and_clean_data(file_path):
df = pd.read_csv(file_path)
# 只看我们需要用到的部分,避免无关列干扰
# 以Give Me Some Credit数据集为参考,列出常见字段
feature_cols = [
'RevolvingUtilizationOfUnsecuredLines', # 信用卡及信用额度使用率
'age', # 年龄
'NumberOfTime30-59DaysPastDueNotWorse', # 过去逾期30-59天的次数
'DebtRatio', # 债务比率
'MonthlyIncome', # 月收入
'NumberOfOpenCreditLinesAndLoans', # 开放式信贷和贷款数量
'NumberOfTimes90DaysLate', # 逾期90天以上次数
'NumberRealEstateLoansOrLines', # 房产贷款数量
'NumberOfTime60-89DaysPastDueNotWorse', # 过去逾期60-89天次数
'NumberOfDependents' # 家属数量
]
label_col = 'SeriousDlqin2yrs' # 标签:是否违约
# 删除缺失值过多的样本
df = df.dropna(subset=feature_cols)
# 特征和标签分离
X = df[feature_cols]
y = df[label_col]
return X, y
def train():
X, y = load_and_clean_data('data/loan_data.csv')
# 处理缺失值:MonthlyIncome用中位数填充
X.loc[:, 'MonthlyIncome'] = X['MonthlyIncome'].fillna(
X['MonthlyIncome'].median()
)
X.loc[:, 'NumberOfDependents'] = X['NumberOfDependents'].fillna(
X['NumberOfDependents'].mode()[0]
)
# 划分训练集和测试集,stratify保证正负样本比例一致
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 随机森林模型
model = RandomForestClassifier(
n_estimators=200, # 树的数量
max_depth=12, # 最大树深,防止过拟合
min_samples_split=10, # 内部节点再划分所需最小样本数
min_samples_leaf=4, # 叶子节点最少样本数
max_features='sqrt', # 每次分裂随机选取sqrt(features)个特征
n_jobs=-1, # 使用全部CPU核心加速训练
random_state=42 # 固定随机种子,保证可复现
)
model.fit(X_train, y_train)
# 预测和评估
y_pred = model.predict(X_test)
y_proba = model.predict_proba(X_test)[:, 1]
print("准确率:", accuracy_score(y_test, y_pred))
print("AUC:", roc_auc_score(y_test, y_proba))
print(classification_report(y_test, y_pred))
# 保存模型和特征列
joblib.dump(model, 'model.pkl')
with open('feature_cols.txt', 'w', encoding='utf-8') as f:
for col in X.columns:
f.write(col + '\n')
if __name__ == '__main__':
train()
几个参数需要重点说明。n_estimators=200表示训练200棵树,这个数值再多效果提升有限,但训练时间会线性增加。max_depth=12用于限制树的深度,避免模型把训练数据背下来。min_samples_split=10和min_samples_leaf=4是进一步防止过拟合的手段,意思是内部节点至少要有10个样本才允许继续分裂,叶子节点上至少要有4个样本。max_features='sqrt'是sklearn对分类问题的默认推荐值,适合大多数场景。
4.3 预测接口与系统集成
模型训练好之后,需要一个Web接口来接收前端传入的数据、调用模型、返回预测结果。这里用Flask实现一个最小可用的后端:
python复制from flask import Flask, request, jsonify, render_template
import joblib
import pandas as pd
import numpy as np
app = Flask(__name__)
# 加载模型和特征列,模型文件在启动时加载一次
model = joblib.load('model.pkl')
@app.route('/')
def index():
return render_template('index.html')
@app.route('/predict', methods=['POST'])
def predict():
# 前端通过JSON传入一条或多条数据
data = request.get_json()
# 构造DataFrame,字段顺序必须与训练时一致
feature_cols = [
'RevolvingUtilizationOfUnsecuredLines',
'age',
'NumberOfTime30-59DaysPastDueNotWorse',
'DebtRatio',
'MonthlyIncome',
'NumberOfOpenCreditLinesAndLoans',
'NumberOfTimes90DaysLate',
'NumberRealEstateLoansOrLines',
'NumberOfTime60-89DaysPastDueNotWorse',
'NumberOfDependents'
]
input_df = pd.DataFrame([data], columns=feature_cols)
# 预测违约概率
probability = model.predict_proba(input_df)[0][1]
prediction = model.predict(input_df)[0]
# 设定一个业务阈值,比如违约概率超过0.5则拒绝放款
threshold = 0.5
recommendation = "建议拒绝" if probability >= threshold else "建议通过"
return jsonify({
"prediction": int(prediction),
"probability_overdue": round(float(probability), 4),
"recommendation": recommendation
})
if __name__ == '__main__':
app.run(debug=True, port=5000)
这段代码的关键点在于,输入数据的特征顺序必须与训练时完全一致,否则模型预测结果会非常离谱。所以我在训练模块里特意保存了一份feature_cols.txt,后端读取后可以动态构造DataFrame,避免手写字段顺序出错。
4.4 前端页面与演示细节
前端部分不需要过度设计,但也不能太粗糙。用一个表单页面,把十个特征做成输入框,底部加一个“预测贷款可能性”的按钮,点击后通过fetch调用后端的/predict接口,把返回的违约概率和推荐结果展示出来。如果想让系统更有说服力,还可以增加“批量预测”功能:上传一个CSV文件,后端批量读取预测,返回一张带预测概率的表格。
我用一个简单的HTML片段来说明核心交互逻辑:
html复制<div class="container">
<h2>贷款可能性预测系统</h2>
<p>请输入客户基本信息,系统将基于随机森林模型给出违约概率和建议</p>
<form id="loanForm">
<label>信用卡使用率</label>
<input type="number" name="RevolvingUtilizationOfUnsecuredLines" step="0.01" required>
<label>年龄</label>
<input type="number" name="age" required>
<label>过去逾期30-59天次数</label>
<input type="number" name="NumberOfTime30-59DaysPastDueNotWorse" required>
<label>债务比率</label>
<input type="number" name="DebtRatio" step="0.01" required>
<label>月收入</label>
<input type="number" name="MonthlyIncome" step="100" required>
<button type="submit">开始预测</button>
</form>
<div id="result"></div>
</div>
<script>
document.getElementById('loanForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(e.target);
const payload = Object.fromEntries(formData.entries());
const response = await fetch('/predict', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
});
const result = await response.json();
document.getElementById('result').innerHTML =
'违约概率:' + String(result.probability_overdue * 100).slice(0, 5) + '%<br>' +
'预测结果:' + result.recommendation;
});
</script>
这里的数值字段都要转成数字再传给后端,前端校验可以加,但不是重点。重点是演示时要准备好几组典型数据,一组高违约风险、一组低违约风险,当场切换测试,效果比临时乱填要好得多。
5. 模型评估、参数调优与避坑实录
模型能跑起来只是第一步,课程设计拿高分的关键在于你愿不愿意往深里做一点。评估指标的选择和参数调优的合理性,就是最能拉开差距的地方。
5.1 指标选不对,答辩两行泪
很多初学者会下意识地看着准确率作为模型好坏的标准,这在信贷违约场景里是个大坑。因为这个场景类别严重不平衡,准确率欺骗性很强。假如100个人里只有6个会违约,就算模型把所有样本都判成正常还款,准确率也有94%。但你仔细想想,这个“准确”其实一点用都没有,6个坏客户全被放过去了,一旦违约,损失可能是几千几万倍于利息收入。
课程设计中更合理的评估指标是AUC和召回率。AUC衡量的是模型把违约客户排在正常客户前面的概率,数值越接近1越好,0.5相当于瞎猜。召回率衡量的是“所有真正违约的人里,模型找回了多少”,在风控场景里,漏掉一个违约者的代价比误杀一个正常客户要高得多。
在sklearn里,这些指标都很好算:
python复制from sklearn.metrics import roc_curve, auc, recall_score
y_proba = model.predict_proba(X_test)[:, 1]
fpr, tpr, _ = roc_curve(y_test, y_proba)
auc_value = auc(fpr, tpr)
print("召回率:", recall_score(y_test, y_pred))
print("AUC:", auc_value)
画ROC曲线的时候注意,横轴是假阳性率,纵轴是真阳性率,曲线越靠近左上角越好。答辩时可以解释一句:“AUC是0.82,说明模型有82%的概率能把违约客户排在正常客户前面。”这句话既直观又专业。
5.2 sklearn调参怎么调最有效率
随机森林不是那种需要几百次迭代调参的模型,但几个核心参数的调整仍然能带来明显的精度提升。我建议按照下面的顺序来调。
第一步,固定n_estimators。在200到500之间尝试,观察AUC变化。如果200棵树和500棵树效果几乎一样,就选200棵,训练时间更短。
第二步,调整max_depth和min_samples_leaf。这两个参数一起控制模型复杂度。如果你发现训练集AUC很高但测试集AUC明显下降,就说明过拟合了,应该减小max_depth,或者增大min_samples_leaf。反之,如果两个AUC都很低,说明模型太简单,应该适当增大深度。
第三步,调整max_features。这个参数控制每棵树的特征随机性。默认的'sqrt'通常已经很好,但在特征数较少的场景,可以尝试None(所有特征参与分裂)或者更大的整数。
不要手动一个一个试,直接用GridSearchCV或者RandomizedSearchCV做网格搜索更高效。下面是一个简化的调参示例:
python复制from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [100, 200, 300],
'max_depth': [8, 10, 12],
'min_samples_leaf': [2, 4, 6],
'max_features': ['sqrt', 'log2']
}
grid_search = GridSearchCV(
RandomForestClassifier(random_state=42, n_jobs=-1),
param_grid,
cv=5,
scoring='roc_auc',
n_jobs=-1
)
grid_search.fit(X_train, y_train)
print("最佳参数:", grid_search.best_params_)
print("最佳AUC:", grid_search.best_score_)
注意scoring='roc_auc'在这里特别重要,因为网格搜索默认用的是准确率,在类别不平衡的数据集上会选出错误的最优参数。我见过不少人忽略这一点,花了几小时调参,结果模型越调越偏。
5.3 现场排查:我踩过的三个坑
第一个坑是数据泄漏。我把SMOTE直接作用在全量数据上,然后再划分训练测试集,结果测试AUC高达0.95,当时还挺开心,后来复查才发现,SMOTE生成的合成样本同时出现在训练集和测试集里,导致评估结果假性偏高。正确做法是先train_test_split,再只对训练集做SMOTE。
第二个坑是模型文件体积过大。我一开始用了1000棵树,保存出来的model.pkl有300多MB,启动Flask后页面响应明显变慢。后来把n_estimators降到200,AUC几乎没下降,但模型文件缩小到60MB,页面响应速度快了很多。课程设计阶段,模型文件大小也是要关注的工程问题。
第三个坑是前端传值和后端字段名匹配不上。有一次我把某个字段名打错了,预测结果一直不对。排查了半天才发现是前端表单字段名与模型训练时的特征列名不一致,模型只能“哑巴吃黄连”,给出的概率完全没有意义。所以后端一定要做字段名校验,接收到请求后先检查字段是否齐全、是否有未知字段,这能省去大量调试时间。
6. 常见问题排查与答辩准备
做课设的过程中会遇到各种稀奇古怪的问题,我把高频问题整理成速查表,方便你遇到问题时能快速定位。同时也分享一下答辩演示时的一些实战心得。
6.1 常见错误速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| CSV文件读取报错“UnicodeDecodeError” | 数据集编码不是UTF-8 | 用pd.read_csv(path, encoding='gbk')或encoding='latin1' |
| 训练时报“Input contains NaN” | 特征列仍有缺失值 | 用X.isnull().sum()检查,并用中位数/众数填充 |
| predict_proba结果顺序不对 | 不清楚类别顺序 | 检查model.classes_,确认索引0和1对应哪个类别 |
| 前端预测结果明显不合理 | 字段顺序或字段名不匹配 | 对比feature_cols.txt与前端传入的字段顺序 |
| 模型训练特别慢 | 树数量过多或n_jobs未设置 | 把n_jobs设为-1,适当减少n_estimators |
| 页面中文显示乱码 | 编码问题或浏览器默认编码错误 | HTML中加<meta charset="utf-8">,代码文件头部加# -*- coding: utf-8 -*- |
| 测试AUC极高但真实使用不准 | 存在数据泄漏或特征泄漏 | 检查是否在划分数据前做了过采样,是否用了未来信息作为特征 |
| 结果全部预测为0 | 类别不平衡太严重 | 使用SMOTE、调整分类阈值,或者改用AUC评估模型 |
6.2 答辩演示的经验
演示的时候,为了让老师对系统留下深刻印象,我建议提前准备一组“对照组”数据:一组是低风险客户(收入高、负债低、无逾期记录),系统输出“建议通过”;一组是高风险客户(收入低、债务比率高、多次逾期),系统输出“建议拒绝”。对比展示比单独输入一串数字更有说服力,因为老师一眼就能看出系统逻辑是合理的。
讲清楚随机森林的“随机性”非常重要。你可以现场画一棵树的决策路径,或者直接打印出特征重要性排名。比如“月度收入”和“信用卡使用率”的重要性排在最前面,这就说明模型确实学到了金融风控里核心的信号。
对于后续扩展方向,可以提两个思路:第一,把随机森林换成XGBoost或LightGBM,理论上精度还能提升;第二,引入评分卡机制,把模型输出的概率映射成300到900之间的信贷评分,更贴近银行真实业务。这些内容放在报告“展望”部分即可,不需要在系统中真正实现。
我个人在演示前最后做的事,是重新从零运行一遍训练脚本,再用测试数据完整走一遍预测流程。这个习惯帮我避免了不少“演示现场翻车”。项目代码能稳定复现,比讲一百句漂亮话都有用。
