目录
一.交叉验证
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%