importwarnings
importlightgbm as lgb
importmatplotlib.pyplot as plt
importnumpy as np
importpandas as pd
fromsklearn.metricsimportaccuracy_score
fromsklearn.model_selectionimportGridSearchCV, RandomizedSearchCV, train_test_split
fromsklearn.neighborsimportKNeighborsClassifier
fromsklearn.preprocessingimportLabelEncoder, StandardScaler
fromskoptimportBayesSearchCV
warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif']=['SimHei','Microsoft YaHei']# 优先黑体,备选微软雅黑
plt.rcParams['axes.unicode_minus']=False# 解决坐标轴负号显示方框问题
data=pd.read_csv(r'D:\Desktop\Postgraduate\PythonStudy\Python打卡训练营\Python打卡训练营\python60-days-challenge-master\data.csv')# 读取数据
# print(data.info())
col_cn_dict={
"Id":"编号",
"Home Ownership":"房屋产权情况",
"Annual Income":"年收入",
"Years in current job":"现工作任职年限",
"Tax Liens":"税务留置次数",
"Number of Open Accounts":"未结清账户数量",
"Years of Credit History":"信用历史年限",
"Maximum Open Credit":"最大开放授信额度",
"Number of Credit Problems":"信用问题次数",
"Months since last delinquent":"上次逾期距今月数",
"Bankruptcies":"破产记录次数",
"Purpose":"贷款用途",
"Term":"贷款期限",
"Current Loan Amount":"当前贷款金额",
"Current Credit Balance":"当前信贷余额",
"Monthly Debt":"月负债",
"Credit Score":"信用评分",
"Credit Default":"是否违约"
}
data=data.rename(columns=col_cn_dict)
# print(data.info())
# print(data['现工作任职年限'].value_counts())
defpreprocess_credit_data(data, test_size=0.2, random_state=42):
"""
信贷违约数据完整预处理流程
返回: X_train, X_test, y_train, y_test
"""
# ==================== 1. 划分特征与标签 ====================
X=data.drop(columns=["是否违约","编号"])# 编号无预测意义,丢弃
y=data["是否违约"]
# ==================== 2. 划分训练集 / 测试集 ====================
X_train, X_test, y_train, y_test=train_test_split(
X, y, test_size=test_size, random_state=random_state, stratify=y
)
# stratify=y 保证训练/测试集中违约比例一致
# ==================== 3. 区分离散 / 连续特征 ====================
discrete_cols=X_train.select_dtypes(include=["object"]).columns.tolist()
continuous_cols=X_train.select_dtypes(include=[np.number]).columns.tolist()
special_col="现工作任职年限"
ifspecial_colindiscrete_cols:
discrete_cols.remove(special_col)# 任职年限单独字典映射
# ==================== 4. 缺失值填充 ====================
# 连续特征 → 中位数(只用训练集统计量,防泄露)
forcolincontinuous_cols:
median_val=X_train[col].median()
X_train[col]=X_train[col].fillna(median_val)
X_test[col]=X_test[col].fillna(median_val)
# 离散特征(含任职年限) → 众数
forcolindiscrete_cols+[special_col]:
mode_val=X_train[col].mode()[0]
X_train[col]=X_train[col].fillna(mode_val)
X_test[col]=X_test[col].fillna(mode_val)
# ==================== 5. 现工作任职年限:字典映射 ====================
job_years_map={
"< 1 year":0,
"1 year":1,
"2 years":2,
"3 years":3,
"4 years":4,
"5 years":5,
"6 years":6,
"7 years":7,
"8 years":8,
"9 years":9,
"10+ years":11,
}
X_train[special_col]=X_train[special_col].map(job_years_map).astype(int)
X_test[special_col]=X_test[special_col].map(job_years_map).astype(int)
# ==================== 6. 其余离散特征编码 ====================
# 有序特征:贷款期限(短期 < 长期)→ 标签编码
# 无序特征:房屋产权情况、贷款用途 → 独热编码
ordinal_cols=["贷款期限"]
nominal_cols=[cforcindiscrete_colsifcnotinordinal_cols]
# 6.1 有序特征 LabelEncoder
forcolinordinal_cols:
le=LabelEncoder()
X_train[col]=le.fit_transform(X_train[col])
X_test[col]=le.transform(X_test[col])
# 6.2 无序特征 One-Hot(测试集按训练集列对齐,防止列数不一致)
X_train=pd.get_dummies(X_train, columns=nominal_cols, drop_first=True)
X_test=pd.get_dummies(X_test, columns=nominal_cols, drop_first=True)
X_test=X_test.reindex(columns=X_train.columns, fill_value=0)
# ==================== 7. 异常值处理(IQR 截断,不删样本) ====================
# 对连续特征 + 映射后的任职年限做截断;独热列和0/1标签列不动
outlier_cols=continuous_cols+[special_col]
forcolinoutlier_cols:
Q1=X_train[col].quantile(0.25)
Q3=X_train[col].quantile(0.75)
IQR=Q3-Q1
lower, upper=Q1-1.5*IQR, Q3+1.5*IQR
X_train[col]=X_train[col].clip(lower=lower, upper=upper)
X_test[col]=X_test[col].clip(lower=lower, upper=upper)
# ==================== 8. 标准化 ====================
# 选择 StandardScaler:已做 IQR 截断,异常值影响可控;
# 信贷数据右偏明显,StandardScaler 比 MinMax 更稳健,且适配逻辑回归/SVM/NN
# 独热列保持 0/1 不缩放,仅缩放连续/有序数值列
onehot_cols=[cforcinX_train.columns
ifany(c.startswith(nc+"_")forncinnominal_cols)]
scale_cols=[cforcinX_train.columnsifcnotinonehot_cols]
scaler=StandardScaler()
X_train[scale_cols]=scaler.fit_transform(X_train[scale_cols])
X_test[scale_cols]=scaler.transform(X_test[scale_cols])
returnX_train, X_test, y_train, y_test
# ========== 调用方式 ==========
X_train, X_test, y_train, y_test=preprocess_credit_data(data)
# ===================== 1. 定义保存结果列表 =====================
results=[]
# ===================== 2. 通用工具函数:保存结果 =====================
defrecord_result(model_name, search_name, best_params,
X_train, X_test, y_train, y_test, best_estimator):
y_pred_train=best_estimator.predict(X_train)
y_pred_test=best_estimator.predict(X_test)
acc_train=round(accuracy_score(y_train, y_pred_train),4)
acc_test=round(accuracy_score(y_test, y_pred_test),4)
results.append({
"模型": model_name,
"寻优方法": search_name,
"最优参数": best_params,
"训练集准确率": acc_train,
"测试集准确率": acc_test
})
# ===================== 3. KNN 参数空间 =====================
knn=KNeighborsClassifier()
knn_grid={
"n_neighbors": [3,5,7,9,11],
"weights": ["uniform","distance"],
"p": [1,2]
}
knn_random={
"n_neighbors":list(range(3,15)),
"weights": ["uniform","distance"],
"p": [1,2]
}
knn_bayes={
"n_neighbors": (3,15),
"weights": ["uniform","distance"],
"p": (1,2)
}
# -------- KNN - 网格搜索 --------
print("==== KNN 网格搜索 ====")
gs_knn=GridSearchCV(knn, knn_grid, cv=5, scoring="accuracy", n_jobs=-1)
gs_knn.fit(X_train, y_train)
record_result("KNN","网格搜索", gs_knn.best_params_, X_train, X_test, y_train, y_test, gs_knn.best_estimator_)
# -------- KNN - 随机搜索 --------
print("==== KNN 随机搜索 ====")
rs_knn=RandomizedSearchCV(knn, knn_random, cv=5, scoring="accuracy",
n_iter=10, random_state=42, n_jobs=-1)
rs_knn.fit(X_train, y_train)
record_result("KNN","随机搜索", rs_knn.best_params_, X_train, X_test, y_train, y_test, rs_knn.best_estimator_)
# -------- KNN - 贝叶斯优化 --------
print("==== KNN 贝叶斯优化 ====")
bs_knn=BayesSearchCV(knn, knn_bayes, cv=5, scoring="accuracy",
n_iter=10, random_state=42, n_jobs=-1)
bs_knn.fit(X_train, y_train)
record_result("KNN","贝叶斯优化", bs_knn.best_params_, X_train, X_test, y_train, y_test, bs_knn.best_estimator_)
# ===================== 4. LightGBM 参数空间 =====================
lgb_clf=lgb.LGBMClassifier(random_state=42, verbosity=-1)
lgb_grid={
"learning_rate": [0.01,0.05,0.1],
"n_estimators": [50,100,200],
"max_depth": [3,5,7]
}
lgb_random={
"learning_rate": np.logspace(-3,-1,10),
"n_estimators":list(range(50,300,20)),
"max_depth":list(range(2,10)),
"subsample": [0.6,0.7,0.8,0.9,1.0]
}
lgb_bayes={
"learning_rate": (1e-3,0.3,"log-uniform"),
"n_estimators": (50,300),
"max_depth": (2,10),
"subsample": (0.6,1.0)
}
# -------- LightGBM - 网格搜索 --------
print("==== LightGBM 网格搜索 ====")
gs_lgb=GridSearchCV(lgb_clf, lgb_grid, cv=5, scoring="accuracy", n_jobs=-1)
gs_lgb.fit(X_train, y_train)
record_result("LightGBM","网格搜索", gs_lgb.best_params_, X_train, X_test, y_train, y_test, gs_lgb.best_estimator_)
# -------- LightGBM - 随机搜索 --------
print("==== LightGBM 随机搜索 ====")
rs_lgb=RandomizedSearchCV(lgb_clf, lgb_random, cv=5, scoring="accuracy",
n_iter=10, random_state=42, n_jobs=-1)
rs_lgb.fit(X_train, y_train)
record_result("LightGBM","随机搜索", rs_lgb.best_params_, X_train, X_test, y_train, y_test, rs_lgb.best_estimator_)
# -------- LightGBM - 贝叶斯优化 --------
print("==== LightGBM 贝叶斯优化 ====")
bs_lgb=BayesSearchCV(lgb_clf, lgb_bayes, cv=5, scoring="accuracy",
n_iter=10, random_state=42, n_jobs=-1)
bs_lgb.fit(X_train, y_train)
record_result("LightGBM","贝叶斯优化", bs_lgb.best_params_, X_train, X_test, y_train, y_test, bs_lgb.best_estimator_)
# ===================== 5. 输出汇总对比表格 =====================
df_result=pd.DataFrame(results)
# 让 Pandas 在终端中正确计算中文字符宽度,避免表头错位
pd.set_option("display.unicode.east_asian_width",True)
pd.set_option("display.unicode.ambiguous_as_wide",True)
# 主表只展示适合横向比较的字段,较长的参数字典放到下方分行显示
summary_columns=["模型","寻优方法","训练集准确率","测试集准确率"]
df_summary=df_result[summary_columns].copy()
accuracy_formatters={
"训练集准确率":lambdavalue: f"{value:.2%}",
"测试集准确率":lambdavalue: f"{value:.2%}",
}
print("\n"+"="*72)
print("【各模型不同超参数寻优方法结果汇总表】")
print("="*72)
print(
df_summary.to_string(
index=False,
justify="center",
col_space={"模型":12,"寻优方法":12},
formatters=accuracy_formatters,
)
)
print("\n【各模型最优参数明细】")
print("-"*72)
forresultinresults:
print(f"{result['模型']} - {result['寻优方法']}")
forparam_name, param_valueinresult["最优参数"].items():
print(f" {param_name}: {param_value}")
print()
|