逻辑回归 银行贷款资格判断案列优化 交叉验证,调整阈值,下采样与过采样方法

目录

一.交叉验证

1.参数选择与验证集的引入

2.交叉验证方法

3.交叉验证实现

二.阈值的调整

1.什么是阈值

2.为什么要调整阈值

①业务目标的权衡:精确率与召回率的博弈

②样本不平衡问题

3.如何优化阈值

三.数据不均衡的处理方法

①下采样

1.下采样方法讲解

2.下采样优化后的完整银行贷款资格案例代码

3.下采样的实验结果

②过采样

1.过采样方法讲解

2.模型训练与优化

3.过采样优化后的完整银行贷款资格案例代码

4.过采样的实验结果


一.交叉验证

1.参数选择与验证集的引入

  • 直接使用测试集选择参数C会导致数据泄露(测试集间接参与训练),不科学。
  • 解决方案:从训练集中再划分验证集(如5万条),用于参数调优,测试集仅用于最终评估。
  • 验证集与测试集区别:验证集用于参数选择,测试集用于最终模型性能验证。
  • 测试集绝对不可参与训练或参数选择,仅用于最终模型评估。

2.交叉验证方法

  • 为充分利用数据,采用K折交叉验证 (如10折):
    • 将训练集均分为K份(如10份),轮流用其中1份作为验证集,其余K-1份训练模型。
    • 计算K次验证结果的平均值作为参数选择的依据。
  • 优势:避免数据浪费,更科学评估参数效果。

3.交叉验证实现

  • 使用cross_val_score函数(需要导入),传入模型、训练数据、K值(如cv=8)及评估指标(如召回率scoring='recall')。

  • 逻辑回归参数:C(惩罚因子)、正则化方法(L1/L2)、优化算法(如LBFGS)、最大迭代次数(如max_iter=1000)。

  • 对每个C值进行交叉验证,记录平均召回率(如C=1时召回率0.617)。

  • 交叉验证结果需取平均值,对比不同参数性能后选择最优解。

  • 通过np.argmax选择最优参数(最终确定C=1为最佳惩罚因子)。如银行贷款案例的最佳C值选择如下

    python 复制代码
    # 交叉验证
    scores=[]
    c_param_range=[0.01,0.1,1,10,100]
    for i in c_param_range:
        lr = LogisticRegression(C=i,penalty='l2',solver='lbfgs',max_iter=1000)
        score = cross_val_score(lr,train_x,train_y,cv=8,scoring='recall')
        score_mean=sum(score)/len(score)
        scores.append(score_mean)
        print(score_mean)
    best_c=c_param_range[np.argmax(scores)]
    print('best_c=',best_c)

二.阈值的调整

在逻辑回归中,阈值(Threshold)是预测阶段用于将模型输出的概率([0,1] 区间)转化为分类结果(正例 / 负例)的临界值。默认情况下,阈值通常设为 0.5(即概率≥0.5 判为正例,<0.5 判为负例),但在实际应用中,阈值需要根据业务目标和数据特性进行优化调整

1.什么是阈值

逻辑回归的核心是通过 sigmoid 函数将线性输出((z = w^Tx + b))转化为样本属于正例的概率: (P(y=1|x) = 1/(1 + e^{-z}})其中,(P(y=1|x)) 表示样本x属于正例的概率。阈值 是一个人为设定的临界值(记为t),用于决策:

  • 若(P(y=1|x) >= t),则预测为正例((y=1));
  • 若(P(y=1|x) < t),则预测为负例((y=0))。

2.为什么要调整阈值

默认阈值 0.5 的合理性建立在 "正例和负例误判代价相同" 的假设上,但实际业务中,正例和负例的误判代价往往不同,因此需要调整阈值以优化模型的实际效果。具体原因包括:

①业务目标的权衡:精确率与召回率的博弈

模型的分类效果可通过混淆矩阵及衍生指标(精确率、召回率、F1 分数等)评估,而阈值直接影响这些指标:

  • 精确率(Precision):预测为正例的样本中,实际为正例的比例((P = TP/(TP + FP))),关注 "预测正例的准确性";
  • 召回率(Recall):实际为正例的样本中,被正确预测为正例的比例((R = TP/(TP + FN))),关注 "是否漏检正例";
  • 假阳性率(FPR):实际为负例的样本中,被误判为正例的比例((FPR = FP/(FP + TN)));
  • 假阴性率(FNR):实际为正例的样本中,被误判为负例的比例(FNR = FN/(TP + FN))。

阈值调整对指标的影响规律:

  • 提高阈值(t增大):模型更 "严格" 地判断正例,会减少假阳性(FP↓),但可能增加假阴性(FN↑),导致精确率升高、召回率降低;
  • 降低阈值(t减小):模型更 "宽松" 地判断正例,会减少假阴性(FN↓),但可能增加假阳性(FP↑),导致召回率升高、精确率降低。

例如:

  • 癌症筛查中,漏检(FN)的代价远高于误判(FP),因此需降低阈值以提高召回率(尽量不漏掉患者);
  • 垃圾邮件识别中,误判正常邮件为垃圾邮件(FP)的代价更高,因此需提高阈值以保证精确率(减少对正常邮件的干扰)。

②样本不平衡问题

当数据集中正负样本比例失衡(如正样本仅占 5%),默认阈值 0.5 可能导致模型 "偏向" 多数类(负例),正例识别率极低。此时需通过调整阈值平衡正负例的识别效果。

3.如何优化阈值

阈值优化的核心是根据业务目标选择合适的评估指标,通过遍历可能的阈值并计算指标表现,找到最优值,例如银行贷款案例的阈值更改如下:

python 复制代码
thresholds=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]
recalls=[]
for i in thresholds:
    y_predicted_proba=lr.predict_proba(test_x)
    y_predicted_proba=pd.DataFrame(y_predicted_proba)
    y_predicted_proba=y_predicted_proba.drop([0],axis=1)
    y_predicted_proba[y_predicted_proba[[1]] > i] = 1
    y_predicted_proba[y_predicted_proba[[1]] <= i] = 0
    recall=metrics.recall_score(test_y,y_predicted_proba)
    recalls.append(recall)
    print("{} Recall metric in the testing dataset:{:.3f}".format(i,recall))
best_t = thresholds[np.argmax(recalls)]
best_calls = max(recalls)
print("最佳阈值为:{} 对应的recalls为:{:.3f}".format(best_t,best_calls))

阈值选择逻辑

  • 若业务要求 "高精确率"(如推荐系统,避免推荐无关内容),选择 PR 曲线上精确率较高的点对应的阈值;
  • 若业务要求 "高召回率"(如欺诈检测,尽量捕捉所有欺诈),选择 PR 曲线上召回率较高的点对应的阈值;
  • 若需平衡两者,选择 F1 分数((F1 = 2×(P ×R)/(P + R)))最大的点对应的阈值。

三.数据不均衡的处理方法

例如银行贷款案例中训练集中类别为0的样本约28万条,类别为1的样本仅492条,导致模型性能受限。有两种解决方法:下采样和过采样方法

①下采样

1.下采样方法讲解

  • 下采样通过减少数据量(如从28万条中抽取720条)实现类别均衡,但可能遗漏部分未训练的特殊数据。

我们从类别为0的样本中随机抽取与类别为1等量的样本(391条),组合成均衡数据集(共782条),代码如下

python 复制代码
# 下采样操作
positive_eg=train_data[train_data['Class']==0]
negative_eg=train_data[train_data['Class']==1]

positive_eg=positive_eg.sample(len(negative_eg))
# 拼接数据
data_c=pd.concat([positive_eg,negative_eg])
train_x=data_c.iloc[:,:-1]
train_y=data_c.iloc[:,-1]

2.下采样优化后的完整银行贷款资格案例代码

python 复制代码
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn import metrics


data = pd.read_csv('creditcard.csv')
scaler=StandardScaler()
data['Amount']=scaler.fit_transform(data[['Amount']])
data = data.drop(['Time'],axis=1)

X=data.iloc[:,:-1]
y=data.iloc[:,-1]
train_x,test_x,train_y,test_y=train_test_split(X,y,test_size=0.3,random_state=400)

train_x['Class'] = train_y
train_data=train_x
# 下采样操作
positive_eg=train_data[train_data['Class']==0]
negative_eg=train_data[train_data['Class']==1]

positive_eg=positive_eg.sample(len(negative_eg))
# 拼接数据
data_c=pd.concat([positive_eg,negative_eg])
train_x=data_c.iloc[:,:-1]
train_y=data_c.iloc[:,-1]

# 交叉验证
scores=[]
c_param_range=[0.01,0.1,1,10,100]
for i in c_param_range:
    lr = LogisticRegression(C=i,penalty='l2',solver='lbfgs',max_iter=1000)
    score = cross_val_score(lr,train_x,train_y,cv=5,scoring='recall')
    score_mean=sum(score)/len(score)
    scores.append(score_mean)
    print(score_mean)
best_c=c_param_range[np.argmax(scores)]
print('best_c=',best_c)

lr=LogisticRegression(C=best_c)
lr.fit(train_x,train_y)
predicted=lr.predict(test_x)
print(metrics.classification_report(test_y,predicted))
# 更改阈值
thresholds=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]
recalls=[]
for i in thresholds:
    y_predicted_proba=lr.predict_proba(test_x)
    y_predicted_proba=pd.DataFrame(y_predicted_proba)
    y_predicted_proba=y_predicted_proba.drop([0],axis=1)
    y_predicted_proba[y_predicted_proba[[1]] > i] = 1
    y_predicted_proba[y_predicted_proba[[1]] <= i] = 0
    recall=metrics.recall_score(test_y,y_predicted_proba)
    recalls.append(recall)
    print("{} Recall metric in the testing dataset:{:.3f}".format(i,recall))
best_t = thresholds[np.argmax(recalls)]
best_calls = max(recalls)
print("最佳阈值为:{} 对应的recalls为:{:.3f}".format(best_t,best_calls))


0.8657142857142859
0.9085714285714286
0.9142857142857143
0.9142857142857143
0.9114285714285714
best_c= 1
              precision    recall  f1-score   support

           0       1.00      0.98      0.99     85301
           1       0.06      0.89      0.11       142

    accuracy                           0.98     85443
   macro avg       0.53      0.94      0.55     85443
weighted avg       1.00      0.98      0.99     85443
0.1 Recall metric in the testing dataset:0.965
0.2 Recall metric in the testing dataset:0.923
0.3 Recall metric in the testing dataset:0.901
0.4 Recall metric in the testing dataset:0.901
0.5 Recall metric in the testing dataset:0.894
0.6 Recall metric in the testing dataset:0.894
0.7 Recall metric in the testing dataset:0.873
0.8 Recall metric in the testing dataset:0.866
0.9 Recall metric in the testing dataset:0.845
最佳阈值为:0.1 对应的recalls为:0.965

3.下采样的实验结果

  • 使用均衡化数据集训练后,模型召回率显著提升至89%
  • 调整阈值后召回率达到98.8%
  • 测试集表现稳定),未出现过拟合,说明下采样有效提取了代表性特征。
  • 副作用:精确率下降至0.06(预测为1的样本中仅6%正确),但业务更关注召回率(真实为1的样本中89%被找出)。
  • 下采样通过减少数据量(如从28万条中抽取720条)实现类别均衡,但可能遗漏部分未训练的特殊数据。

②过采样

1.过采样方法讲解

  • 过采样通过人工合成少数类数据(如从391条增至19.9万条)实现数据均衡。

  • 核心算法为SMOTE(合成少数类过采样技术),基于K近邻原理在少数类数据点间生成新样本。

  • 需安装imblearn库调用SMOTE函数,算法自动识别少数类并拟合。

  • 示例中拟合后数据量达45万条,需注意生成数据可能存在噪声。

    python 复制代码
    # 过采样操作
    from imblearn.over_sampling import SMOTE
    oversample=SMOTE(random_state=0)#保证数据拟合效果,随机种子
    os_x_train,os_y_train=oversample.fit_resample(train_x,train_y)
    
    train_x,test_x,train_y,test_y=train_test_split(os_x_train,os_y_train,test_size=0.3,random_state=400)

2.模型训练与优化

  • 过采样后数据需再次切分为训练集和测试集,以验证模型效果
  • 可以通过调整分类阈值进一步提升正确率

3.过采样优化后的完整银行贷款资格案例代码

python 复制代码
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn import metrics

data = pd.read_csv('creditcard.csv')
scaler=StandardScaler()
data['Amount']=scaler.fit_transform(data[['Amount']])
data = data.drop(['Time'],axis=1)

X=data.iloc[:,:-1]
y=data.iloc[:,-1]
train_x,test_x,train_y,test_y=train_test_split(X,y,test_size=0.3,random_state=400)

# 过采样操作
from imblearn.over_sampling import SMOTE
oversample=SMOTE(random_state=0)#保证数据拟合效果,随机种子
os_x_train,os_y_train=oversample.fit_resample(train_x,train_y)

train_x,test_x,train_y,test_y=train_test_split(os_x_train,os_y_train,test_size=0.3,random_state=400)

# 交叉验证
scores=[]
c_param_range=[0.01,0.1,1,10,100]
for i in c_param_range:
    lr = LogisticRegression(C=i,penalty='l2',solver='lbfgs',max_iter=1000)
    score = cross_val_score(lr,train_x,train_y,cv=8,scoring='recall')
    score_mean=sum(score)/len(score)
    scores.append(score_mean)
    print(score_mean)
best_c=c_param_range[np.argmax(scores)]
print('best_c=',best_c)

lr=LogisticRegression(C=best_c)
lr.fit(train_x,train_y)
predicted=lr.predict(test_x)
print('未调整阈值的报告:'+'\t'+metrics.classification_report(test_y,predicted))

# 更改阈值
thresholds=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9]
recalls=[]
for i in thresholds:
    global y_predicted_proba
    y_predicted_proba=lr.predict_proba(test_x)
    y_predicted_proba=pd.DataFrame(y_predicted_proba)
    y_predicted_proba=y_predicted_proba.drop([0],axis=1)
    y_predicted_proba[y_predicted_proba[[1]] > i] = 1
    y_predicted_proba[y_predicted_proba[[1]] <= i] = 0
    recall=metrics.recall_score(test_y,y_predicted_proba)
    recalls.append(recall)
    # print("{} Recall metric in the testing dataset:{:.3f}".format(i,recall))
best_t = thresholds[np.argmax(recalls)]
best_calls = max(recalls)
print("最佳阈值为:{} 对应的recalls为:{:.3f}".format(best_t,best_calls))

0.9259697681664929
0.9305118963803862
0.931258151444497
0.9313873112679044
0.9313873112679044
best_c= 10
未调整阈值的报告: precision    recall  f1-score   support

           0       0.93      0.98      0.96     59757
           1       0.98      0.93      0.95     59652

    accuracy                           0.95    119409
   macro avg       0.96      0.95      0.95    119409
weighted avg       0.96      0.95      0.95    119409

最佳阈值为:0.1 对应的recalls为:0.988

4.过采样的实验结果

  • 模型召回率显著提升至93%
  • 调整阈值后召回率达到98.8%
相关推荐
小指纹1 小时前
图论-最短路Dijkstra算法
数据结构·c++·算法·深度优先·图论
2501_924878732 小时前
无人机光伏巡检缺陷检出率↑32%:陌讯多模态融合算法实战解析
开发语言·人工智能·算法·视觉检测·无人机
沉睡的无敌雄狮2 小时前
无人机光伏巡检漏检率↓78%!陌讯多模态融合算法实战解析
人工智能·算法·计算机视觉·目标跟踪
magicwt2 小时前
《从零构建大模型》读书笔记
算法
大胖猫L2 小时前
深搜与广搜在 TypeScript 类型递归中的应用
前端·算法
2202_756749693 小时前
02 基于sklearn的机械学习-KNN算法、模型选择与调优(交叉验证、朴素贝叶斯算法、拉普拉斯平滑)、决策树(信息增益、基尼指数)、随机森林
python·算法·决策树·随机森林·机器学习·sklearn
ATaylorSu3 小时前
经典算法之美:冒泡排序的优雅实现
开发语言·笔记·学习·算法
菜鸡nan4 小时前
23th Day| 39.组合总和,40.组合总和II,131.分割回文串
算法·leetcode·职场和发展
冷月葬花~4 小时前
day37 卡码网52. 携带研究材料 力扣518.零钱兑换II 力扣377. 组合总和 Ⅳ 卡码网57. 爬楼梯
算法