超参数调整专题1
知识点回顾
今日作业:
对于信贷数据的其他模型,如LightGBM和KNN 尝试用下贝叶斯优化和网格搜索
# %% [markdown]
# # DAY10
#
# 1. 把之前所有的处理手段都处理一遍,回顾一下全流程,以后就用处理好的部分直接完成
# 2. 开始机器学习建模(简单建模,不涉及调参)和评估
#
#
# %% [markdown]
# ## 预处理流程回顾
#
# 1. 导入库
# 2. 读取数据查看数据信息--理解数据
# 3. 缺失值处理
# 4. 异常值处理
# 5. 离散值处理
# 6. 删除无用列
# 7. 划分数据集
# 8. 特征工程
# 9. 模型训练
# 10. 模型评估
# 11. 模型保存
# 12. 模型预测
# %% [markdown]
# ### 导入所需要的包
#
# 这里其实是写完后一起整理到这里的
# %%
import pandas as pd
import pandas as pd #用于数据处理和分析,可处理表格数据。
import numpy as np #用于数值计算,提供了高效的数组操作。
import matplotlib.pyplot as plt #用于绘制各种类型的图表
import seaborn as sns #基于matplotlib的高级绘图库,能绘制更美观的统计图形。
# 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
# %% [markdown]
# ### 查看数据信息
# %%
data = pd.read_csv('data.csv') #读取数据
print("数据基本信息:")
data.info()
print("\n数据前5行预览:")
print(data.head())
# %% [markdown]
# 以下是对该数据集进行预处理的步骤及顺序:
#
# 一共有17个特征,分别处理
#
#
# 1. **缺失值处理**
# - **Annual Income**:有5943个非空值,存在缺失值。可以考虑使用均值填充、中位数填充或者基于其他相关特征进行回归预测填充。例如,如果“Home Ownership”和“Annual Income”有一定相关性,可根据不同房屋所有权类型的平均收入来填充缺失值。
# - **Years in current job**:7129个非空值,存在缺失值。由于是对象类型,可能需要先将其转换为合适的数值类型再进行处理。比如将“10+ years”转换为10,“8 years”转换为8等,然后再用众数或中位数填充缺失值。
# - **Months since last delinquent**:只有3419个非空值,缺失值较多。若该特征对目标变量影响较大,可尝试用多重填补法等较为复杂的方法进行填充;若影响较小,也可直接删除含有缺失值的行,但要注意可能会导致数据量损失较大。
# - **Credit Score**:5943个非空值,存在缺失值。可参照“Annual Income”的处理方式,根据与其他特征的相关性来选择合适的填充方法。
# 2. **数据类型转换**
# - **Years in current job**:将其从对象类型转换为数值类型,方便后续的计算和模型处理。
# - **Home Ownership**、**Purpose**、**Term**:这些对象类型的特征可以进行独热编码或标签编码。如果特征的类别数较少且没有明显的顺序关系,独热编码较为合适;如果有一定的顺序关系,如“Term”的“Short Term”和“Long Term”,可以考虑标签编码。
# 3. **异常值处理**
# - 对于数值型特征,如“Annual Income”“Current Loan Amount”等,可以通过箱线图等方法检测异常值。如果存在异常值,需根据实际情况决定是否进行处理。若是数据录入错误等原因导致的异常值,可以进行修正或删除;若是真实存在的极端值,可能需要保留,但在某些模型中可能需要进行特殊处理,如采用稳健的统计方法或对数据进行变换。
# 4. **特征缩放**
# - 对数值型特征进行特征缩放,将其缩放到相同的尺度,以避免某些特征因数值较大而在模型中占据主导地位。常用的方法有Min - Max标准化和Z - score标准化。例如,“Annual Income”“Years of Credit History”“Credit Score”等特征的取值范围差异较大,可通过特征缩放将它们的取值范围统一到[0, 1]或均值为0、标准差为1的分布上。
# 5. **特征工程**
# - **衍生新特征**:根据已有特征创建新的特征,可能会对模型性能有提升。例如,可以计算“Debt - to - Income Ratio”(负债收入比),即“Monthly Debt”与“Annual Income”的比值,来反映客户的债务负担情况。
# - **特征选择**:通过相关性分析等方法,选择与目标变量“Credit Default”相关性较高的特征,去除相关性较低或冗余的特征,以降低模型的复杂度和过拟合的风险。
#
# 在实际操作中,需要先进行缺失值处理,然后进行数据类型转换,接着处理异常值,再进行特征缩放,最后进行特征工程。这样的顺序可以保证数据在预处理过程中的一致性和有效性,为后续的机器学习模型训练提供高质量的数据。
# %% [markdown]
# ### 首先处理object对象
#
# 因为最后的输入都是数值类型,所以先给字符串变量处理了
# %%
# 先筛选字符串变量
discrete_features = data.select_dtypes(include=['object']).columns.tolist()
discrete_features
# %%
# 依次查看内容
for feature in discrete_features:
print(f"\n{feature}的唯一值:")
print(data[feature].value_counts())
# %% [markdown]
# Home Ownership需要标签编码。
# - 住房抵押贷款:3637 这个是有房贷,有房子
# - 租房:3204 没房子
# - 拥有自有住房:647 这个没贷款,有房子
# - 有贷款:12 这个是有其他贷款,有房子,没房贷
# - 名称:房屋所有权,数据类型:int64
#
#
# 按照贷款严重程度(抗风险能力),依次是:自有住房 < 租房 < 有其他贷款 < 住房抵押贷款
#
# 所以按照这个逻辑来进行编码
#
# Years in current job 做标签编码
#
# Purpose做独热编码
#
# Term直接做0-1映射即可,二分类问题,处理后更改自己的名字为1的类别
#
#
# %%
# Home Ownership 标签编码
home_ownership_mapping = {
'Own Home': 1,
'Rent': 2,
'Have Mortgage': 3,
'Home Mortgage': 4
}
data['Home Ownership'] = data['Home Ownership'].map(home_ownership_mapping)
# Years in current job 标签编码
years_in_job_mapping = {
'< 1 year': 1,
'1 year': 2,
'2 years': 3,
'3 years': 4,
'4 years': 5,
'5 years': 6,
'6 years': 7,
'7 years': 8,
'8 years': 9,
'9 years': 10,
'10+ years': 11
}
data['Years in current job'] = data['Years in current job'].map(years_in_job_mapping)
# Purpose 独热编码,记得需要将bool类型转换为数值
data = pd.get_dummies(data, columns=['Purpose'])
data2 = pd.read_csv("data.csv") # 重新读取数据,用来做列名对比
list_final = [] # 新建一个空列表,用于存放独热编码后新增的特征名
for i in data.columns:
if i not in data2.columns:
list_final.append(i) # 这里打印出来的就是独热编码后的特征名
for i in list_final:
data[i] = data[i].astype(int) # 这里的i就是独热编码后的特征名
# Term 0 - 1 映射
term_mapping = {
'Short Term': 0,
'Long Term': 1
}
data['Term'] = data['Term'].map(term_mapping)
data.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列
# %% [markdown]
# ### 处理数值型对象
# %% [markdown]
# #### 缺失值填补
# %%
continuous_features = data.select_dtypes(include=['int64', 'float64']).columns.tolist() #把筛选出来的列名转换成列表
# 连续特征用中位数补全
for feature in continuous_features:
mode_value = data[feature].mode()[0] #获取该列的众数。
data[feature].fillna(mode_value, inplace=True) #用众数填充该列的缺失值,inplace=True表示直接在原数据上修改。
# %% [markdown]
# #### 异常值处理
# 异常值一般不处理,或者结合对照试验处理和不处理都尝试下,但是论文中要写这个,作为个工作量
# %%
data.info() #查看数据基本信息
# %% [markdown]
# 此时数据没有缺失值,都是
# %% [markdown]
# ## 可视化分析
# 这部分比较随意,根据自己的需要来绘制图像。作为描述性统计部分 像他人介绍你的数据分布
# %% [markdown]
# # 机器学习模型建模
# %% [markdown]
# 首先进行数据划分,记住之前课上的划分方法:只要调参就要考2次
#
# 这里我们先不调参,所以只需要划分一次即可
#
# %% [markdown]
# ## 数据划分
# %%
# 划分训练集和测试机
from sklearn.model_selection import train_test_split
X = data.drop(['Credit Default'], axis=1) # 特征,axis=1表示按列删除
y = data['Credit Default'] # 标签
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 划分数据集,20%作为测试集,随机种子为42
# 训练集和测试集的形状
print(f"训练集形状: {X_train.shape}, 测试集形状: {X_test.shape}") # 打印训练集和测试集的形状
# %% [markdown]
# ## 模型训练与评估
# 三行经典代码
# 1. 模型实例化
# 2. 模型训练(代入训练集)
# 3. 模型预测 (代入测试集)
#
# 测试集的预测值和测试集的真实值进行对比,得到混淆矩阵
#
# - 基于混淆矩阵,计算准确率、召回率、F1值,这些都是固定阈值的评估指标
#
# - AUC是基于不同阈值得到不同的混淆矩阵,然后计算每个阈值对应FPR和TPR,讲这些点连成线,最后求曲线下的面积,得到AUC值
# %%
# #安装xgboost库
# !pip install xgboost -i https://pypi.tuna.tsinghua.edu.cn/simple/
# #安装lightgbm库
# !pip install lightgbm -i https://pypi.tuna.tsinghua.edu.cn/simple/
# #安装catboost库
# !pip install catboost -i https://pypi.tuna.tsinghua.edu.cn/simple/
# %%
from sklearn.svm import SVC #支持向量机分类器
from sklearn.neighbors import KNeighborsClassifier #K近邻分类器
from sklearn.linear_model import LogisticRegression #逻辑回归分类器
import xgboost as xgb #XGBoost分类器
import lightgbm as lgb #LightGBM分类器
from sklearn.ensemble import RandomForestClassifier #随机森林分类器
from sklearn.tree import DecisionTreeClassifier #决策树分类器
from sklearn.naive_bayes import GaussianNB #高斯朴素贝叶斯分类器
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score # 用于评估分类器性能的指标
from sklearn.metrics import classification_report, confusion_matrix #用于生成分类报告和混淆矩阵
import warnings #用于忽略警告信息
warnings.filterwarnings("ignore") # 忽略所有警告信息
# %%
# SVM
svm_model = SVC(random_state=42)
svm_model.fit(X_train, y_train)
svm_pred = svm_model.predict(X_test)
print("\nSVM 分类报告:")
print(classification_report(y_test, svm_pred)) # 打印分类报告
print("SVM 混淆矩阵:")
print(confusion_matrix(y_test, svm_pred)) # 打印混淆矩阵
# 计算 SVM 评估指标,这些指标默认计算正类的性能
svm_accuracy = accuracy_score(y_test, svm_pred)
svm_precision = precision_score(y_test, svm_pred)
svm_recall = recall_score(y_test, svm_pred)
svm_f1 = f1_score(y_test, svm_pred)
print("SVM 模型评估指标:")
print(f"准确率: {svm_accuracy:.4f}")
print(f"精确率: {svm_precision:.4f}")
print(f"召回率: {svm_recall:.4f}")
print(f"F1 值: {svm_f1:.4f}")
# %% [markdown]
# classification_report它会生成所有类别的指标
#
# 准确率(Accuracy)是一个全局指标,衡量所有类别预测正确的比例 (TP + TN) / (TP + TN + FP + FN)。它不区分正负类,所以它只有一个值,不区分类别
#
# 单独调用的 precision_score, recall_score, f1_score 在二分类中默认只计算正类(标签 1)的性能。由于模型从未成功预测出类别 1(TP=0),所以这些指标对类别 1 来说都是 0。
#
#
# %%
# KNN
knn_model = KNeighborsClassifier()
knn_model.fit(X_train, y_train)
knn_pred = knn_model.predict(X_test)
print("\nKNN 分类报告:")
print(classification_report(y_test, knn_pred))
print("KNN 混淆矩阵:")
print(confusion_matrix(y_test, knn_pred))
knn_accuracy = accuracy_score(y_test, knn_pred)
knn_precision = precision_score(y_test, knn_pred)
knn_recall = recall_score(y_test, knn_pred)
knn_f1 = f1_score(y_test, knn_pred)
print("KNN 模型评估指标:")
print(f"准确率: {knn_accuracy:.4f}")
print(f"精确率: {knn_precision:.4f}")
print(f"召回率: {knn_recall:.4f}")
print(f"F1 值: {knn_f1:.4f}")
# %%
# 逻辑回归
logreg_model = LogisticRegression(random_state=42)
logreg_model.fit(X_train, y_train)
logreg_pred = logreg_model.predict(X_test)
print("\n逻辑回归 分类报告:")
print(classification_report(y_test, logreg_pred))
print("逻辑回归 混淆矩阵:")
print(confusion_matrix(y_test, logreg_pred))
logreg_accuracy = accuracy_score(y_test, logreg_pred)
logreg_precision = precision_score(y_test, logreg_pred)
logreg_recall = recall_score(y_test, logreg_pred)
logreg_f1 = f1_score(y_test, logreg_pred)
print("逻辑回归 模型评估指标:")
print(f"准确率: {logreg_accuracy:.4f}")
print(f"精确率: {logreg_precision:.4f}")
print(f"召回率: {logreg_recall:.4f}")
print(f"F1 值: {logreg_f1:.4f}")
# %%
# 朴素贝叶斯
nb_model = GaussianNB()
nb_model.fit(X_train, y_train)
nb_pred = nb_model.predict(X_test)
print("\n朴素贝叶斯 分类报告:")
print(classification_report(y_test, nb_pred))
print("朴素贝叶斯 混淆矩阵:")
print(confusion_matrix(y_test, nb_pred))
nb_accuracy = accuracy_score(y_test, nb_pred)
nb_precision = precision_score(y_test, nb_pred)
nb_recall = recall_score(y_test, nb_pred)
nb_f1 = f1_score(y_test, nb_pred)
print("朴素贝叶斯 模型评估指标:")
print(f"准确率: {nb_accuracy:.4f}")
print(f"精确率: {nb_precision:.4f}")
print(f"召回率: {nb_recall:.4f}")
print(f"F1 值: {nb_f1:.4f}")
# %%
# 决策树
dt_model = DecisionTreeClassifier(random_state=42)
dt_model.fit(X_train, y_train)
dt_pred = dt_model.predict(X_test)
print("\n决策树 分类报告:")
print(classification_report(y_test, dt_pred))
print("决策树 混淆矩阵:")
print(confusion_matrix(y_test, dt_pred))
dt_accuracy = accuracy_score(y_test, dt_pred)
dt_precision = precision_score(y_test, dt_pred)
dt_recall = recall_score(y_test, dt_pred)
dt_f1 = f1_score(y_test, dt_pred)
print("决策树 模型评估指标:")
print(f"准确率: {dt_accuracy:.4f}")
print(f"精确率: {dt_precision:.4f}")
print(f"召回率: {dt_recall:.4f}")
print(f"F1 值: {dt_f1:.4f}")
# %%
# 随机森林
rf_model = RandomForestClassifier(random_state=42)
rf_model.fit(X_train, y_train)
rf_pred = rf_model.predict(X_test)
print("\n随机森林 分类报告:")
print(classification_report(y_test, rf_pred))
print("随机森林 混淆矩阵:")
print(confusion_matrix(y_test, rf_pred))
rf_accuracy = accuracy_score(y_test, rf_pred)
rf_precision = precision_score(y_test, rf_pred)
rf_recall = recall_score(y_test, rf_pred)
rf_f1 = f1_score(y_test, rf_pred)
print("随机森林 模型评估指标:")
print(f"准确率: {rf_accuracy:.4f}")
print(f"精确率: {rf_precision:.4f}")
print(f"召回率: {rf_recall:.4f}")
print(f"F1 值: {rf_f1:.4f}")
# %%
# XGBoost
xgb_model = xgb.XGBClassifier(random_state=42)
xgb_model.fit(X_train, y_train)
xgb_pred = xgb_model.predict(X_test)
print("\nXGBoost 分类报告:")
print(classification_report(y_test, xgb_pred))
print("XGBoost 混淆矩阵:")
print(confusion_matrix(y_test, xgb_pred))
xgb_accuracy = accuracy_score(y_test, xgb_pred)
xgb_precision = precision_score(y_test, xgb_pred)
xgb_recall = recall_score(y_test, xgb_pred)
xgb_f1 = f1_score(y_test, xgb_pred)
print("XGBoost 模型评估指标:")
print(f"准确率: {xgb_accuracy:.4f}")
print(f"精确率: {xgb_precision:.4f}")
print(f"召回率: {xgb_recall:.4f}")
print(f"F1 值: {xgb_f1:.4f}")
# %%
# LightGBM
lgb_model = lgb.LGBMClassifier(random_state=42)
lgb_model.fit(X_train, y_train)
lgb_pred = lgb_model.predict(X_test)
print("\nLightGBM 分类报告:")
print(classification_report(y_test, lgb_pred))
print("LightGBM 混淆矩阵:")
print(confusion_matrix(y_test, lgb_pred))
lgb_accuracy = accuracy_score(y_test, lgb_pred)
lgb_precision = precision_score(y_test, lgb_pred)
lgb_recall = recall_score(y_test, lgb_pred)
lgb_f1 = f1_score(y_test, lgb_pred)
print("LightGBM 模型评估指标:")
print(f"准确率: {lgb_accuracy:.4f}")
print(f"精确率: {lgb_precision:.4f}")
print(f"召回率: {lgb_recall:.4f}")
print(f"F1 值: {lgb_f1:.4f}")
# %% [markdown]
# | 模型名称 | 准确率 | 精确率(正类) | 召回率(正类) | F1值(正类) | 精确率(负类) | 召回率(负类) | F1值(负类) |
# | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
# | SVM | 0.7060 | 0.0000 | 0.0000 | 0.0000 | 0.71 | 1.00 | 0.83 |
# | KNN | 0.6753 | 0.4102 | 0.2381 | 0.3013 | 0.73 | 0.86 | 0.79 |
# | 逻辑回归 | 0.7560 | 0.8571 | 0.2041 | 0.3297 | 0.75 | 0.99 | 0.85 |
# | 朴素贝叶斯 | 0.4267 | 0.3377 | 0.9887 | 0.5035 | 0.98 | 0.19 | 0.32 |
# | 决策树 | 0.6773 | 0.4564 | 0.5102 | 0.4818 | 0.79 | 0.75 | 0.77 |
# | 随机森林 | 0.7700 | 0.7857 | 0.2993 | 0.4335 | 0.77 | 0.97 | 0.86 |
# | XGBoost | 0.7473 | 0.6192 | 0.3651 | 0.4593 | 0.77 | 0.91 | 0.84 |
# | LightGBM | 0.7660 | 0.7009 | 0.3560 | 0.4722 | 0.78 | 0.94 | 0.85 |
# %% [markdown]
# auc留到后续介绍
#
# PS: 我的感觉,auc是纯纯的垃圾指标 svm是纯纯的垃圾算法
# %%
# --- 2. 网格搜索优化LightGBM ---
print("\n--- 2. Grid search is being used to optimize LightGBM (Training set -> Test set) ---")
from sklearn.model_selection import GridSearchCV
from scipy.stats import randint
import time
n_estimators_list = list(np.random.randint(50, 200, size=10))
max_depth_list = list(np.random.randint(3, 15, size=10))
num_leaves_list = list(np.random.randint(31, 255, size=10))
param_grid = {
'n_estimators': n_estimators_list,
'max_depth': max_depth_list,
'num_leaves': num_leaves_list,
'learning_rate': [0.01, 0.05, 0.1, 0.2],
}
grid_search = GridSearchCV(
estimator=lgb.LGBMClassifier(random_state=42),
param_grid=param_grid,
cv=5,
n_jobs=-1,
scoring='accuracy'
)
start_time = time.time()
grid_search.fit(X_train, y_train)
end_time = time.time()
print(f"searching cost: {end_time - start_time:.4f} seconds")
print("best parameters: ", grid_search.best_params_)
# %%
# --- 2. 贝叶斯优化LightGBM ---
print("\n--- 2. 贝叶斯优化LightGBM (训练集 -> 测试集) ---")
from skopt import BayesSearchCV
from skopt.space import Integer, Real
import time
import lightgbm as lgb
from sklearn.metrics import classification_report, confusion_matrix
# 定义要搜索的参数空间
search_space = {
'n_estimators': Integer(50, 200), # 树的数量
'max_depth': Integer(3, 15), # 树的最大深度
'num_leaves': Integer(31, 255), # 叶子节点数
'learning_rate': Real(0.01, 0.2, prior='log-uniform') # 学习率(对数均匀分布)
}
# 创建贝叶斯优化搜索对象
bayes_search = BayesSearchCV(
estimator=lgb.LGBMClassifier(random_state=42),
search_spaces=search_space,
n_iter=32, # 迭代次数,可根据需要调整
cv=5, # 5折交叉验证
n_jobs=-1, # 使用所有可用的CPU核心
scoring='accuracy',
random_state=42
)
start_time = time.time()
# 在训练集上进行贝叶斯优化搜索
bayes_search.fit(X_train, y_train)
end_time = time.time()
print(f"贝叶斯优化耗时: {end_time - start_time:.4f} 秒")
print("最佳参数: ", bayes_search.best_params_)
# 使用最佳参数的模型进行预测
best_model = bayes_search.best_estimator_
best_pred = best_model.predict(X_test)
print("\n贝叶斯优化后的LightGBM 在测试集上的分类报告:")
print(classification_report(y_test, best_pred))
print("贝叶斯优化后的LightGBM 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, best_pred))
@浙大疏锦行