#####CV
1. Dataset构建
import os # 导入操作系统模块,用于文件路径操作
import random # 导入随机数模块,用于数据打乱
from PIL import Image # 导入图像处理库,用于读取和处理图像
import matplotlib.pyplot as plt # 导入绘图库,用于可视化
import matplotlib as mpl # 导入matplotlib配置模块,用于设置绘图参数
import numpy as np # 导入数值计算库,用于数组操作和数学计算
from collections import defaultdict # 导入默认字典,用于统计
from pathlib import Path # 导入路径处理模块,用于跨平台路径操作
from matplotlib.font_manager import FontProperties # 导入字体管理模块,用于设置中文字体
from tqdm import tqdm # 导入进度条模块,用于显示训练进度
from torch.utils.data import Dataset, Subset # 导入PyTorch数据集相关模块
from torch.utils.data import DataLoader # 导入数据加载器,用于批量加载数据
1.1 获取图片信息
1.2 构建Dataset
class GarbageDataset4(Dataset):
cls_num = 4 # 设置分类数量为4类
def init(self, root_dir_, transform=None, class_names_path=None):
self.root_dir = root_dir_ # 存储数据根目录
self.transform = transform # 存储图像预处理变换
self.img_info = [] # 存储图像信息列表,格式为[(path, label), ...]
self.label_array = None # 存储标签数组
# 检查并加载类别名称文件
if class_names_path:
self.class_names = self.load_class_names(class_names_path)
else:
raise Exception("class_names_path is empty !!") # 如果类别名称文件路径为空,抛出异常
self.map_dict = self.generate_mapping_dict() # 生成类别映射字典
self.class_names = "厨余垃圾 可回收物 其他垃圾 有害垃圾".split() # 设置4分类的类别名称 self._get_img_info() # 获取图像信息
@staticmethod
def load_class_names(class_file_path):
if not os.path.exists(class_file_path): # 检查文件是否存在
print(f"警告: 类别名称文件 '{class_file_path}' 不存在,将使用类别ID作为名称")
return None
# 读取类别名称文件
with open(class_file_path, 'r', encoding='utf-8') as f:
class_names = [line.strip() for line in f.readlines() if line.strip()] # 去除空行和空白字符 return class_names
###########################################考试押题这里开始
def getitem(self, index):
path_img, label = self.img_info[index] # 获取图像路径和标签
img = Image.open(path_img).convert('RGB') # 打开图像并转换为RGB格式
# 对图像进行预处理,通常采用torchvision.transforms对象
if self.transform is not None:
img = self.transform(img) # 应用图像变换
return img, label, path_img # 返回处理后的图像、标签和路径
##########################################考试押题这里结束
def len(self): if len(self.img_info) == 0: # 检查数据集是否为空
raise Exception("\ndata_dir:{} is a empty dir! Please checkout your path to images!".format(
self.root_dir)) # 代码具有友好的提示功能,便于debug
return len(self.img_info) # 返回图像信息列表的长度
def _get_img_info(self):
self.img_info = [] # 初始化图像信息列表
def get_image_files(root_dir, img_extensions=None):
if img_extensions is None:
img_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp', '.tiff'} # 支持的图像格式
image_files = [] # 存储图像文件路径
for root, _, files in os.walk(root_dir): # 递归遍历目录
for file in files: # 遍历文件
ext = os.path.splitext(file)[1].lower() # 获取文件扩展名并转为小写
if ext in img_extensions: # 检查是否为图像文件
image_files.append(os.path.join(root, file)) # 添加图像文件路径
return image_files
# 获取图片路径
image_files = get_image_files(self.root_dir) # 获取所有图像文件路径
# 获取图片标签
image_label = [int(os.path.basename(os.path.dirname(p_))) for p_ in image_files] # 从文件夹名获取原始标签
image_label = [int(self.map_dict[label265]) for label265 in image_label] # 转换为4分类标签
# 存储图片路径、标签信息
self.img_info = [(img_path, image_label) for img_path, image_label in zip(image_files, image_label)]
print(f"成功加载 {len(self.img_info)} 张图像,共 {len(set([info[1] for info in self.img_info]))} 个类别")
return self.img_info
def generate_mapping_dict(self):
prefix_mapping = { # 定义前缀映射关系
"厨余垃圾": 0, # 厨余垃圾映射为0
"可回收物": 1, # 可回收物映射为1
# "可回收垃圾": 1, # 注释掉的映射
"其它垃圾": 2, # 其它垃圾映射为2
"其他垃圾": 2, # 其他垃圾映射为2
"有害垃圾": 3 # 有害垃圾映射为3
}
# 根据类别名称生成新的类别列表
new_class_list = [prefix_mapping[name.split("-")[0]] for name in self.class_names]
# 生成原始类别列表(0到类别数量-1)
old_class_list = [int(i) for i in range(len(self.class_names))]
# 创建映射字典,将0-264映射到0-3
mapping_dict = dict(zip(old_class_list, new_class_list))
return mapping_dict
1.3 加入预处理模块transforms
2. 构建DataLoader(库:DataLoader)
#### !!!数据增强方法(库:Normalize/Resize) import torchvision.transforms as transforms # 导入图像变换模块
train_bs = 64 # 设置训练批次大小为64
workers = 0 # notebook中设置为0,避免多进程问题
data_root_dir = r"/root/autodl-tmp/data/garbage265" # 数据根目录路径
step0: 配置训练和验证数据目录
train_dir = os.path.join(data_root_dir, "train") # 训练数据目录
valid_dir = os.path.join(data_root_dir, "val") # 验证数据目录
###########################################考试押题这里开始
step1:数据集配置
cls_names_path = os.path.join(train_dir, "classname.txt") # 类别名称文件路径
# 设置图像归一化参数(ImageNet数据集的统计值)
norm_mean = [0.485, 0.456, 0.406] # imagenet 120万图像统计得来的均值
norm_std = [0.229, 0.224, 0.225] # imagenet 120万图像统计得来的标准差
normTransform = transforms.Normalize(norm_mean, norm_std) # 创建归一化变换
# 训练数据增强变换
transforms_train = transforms.Compose([
transforms.Resize((256)), # 调整图像大小,最短边为256像素
transforms.CenterCrop(256), # 中心裁剪到256x256
transforms.RandomCrop(224), # 随机裁剪到224x224
transforms.RandomHorizontalFlip(p=0.5), # 随机水平翻转,概率为0.5
transforms.ToTensor(), # 转换为张量
normTransform, # 应用归一化
])
# 验证数据变换(无数据增强)
transforms_valid = transforms.Compose([
transforms.Resize((224, 224)), # 调整图像大小到224x224
transforms.ToTensor(), # 转换为张量
normTransform, # 应用归一化
])
#########################################考试押题这里结束
# 创建训练数据集
train_data_full = GarbageDataset4(root_dir_=train_dir, transform=transforms_train, class_names_path=cls_names_path)
# 创建验证数据集
valid_data = GarbageDataset4(root_dir_=valid_dir, transform=transforms_valid, class_names_path=cls_names_path)
# 选择部分训练数据进行训练(10%)
num_train = int(len(train_data_full) * 0.1) # 计算训练样本数量
indices = list(range(num_train)) # 创建索引列表
train_data_full = Subset(train_data_full, indices) # 根据配置参数,选择训练集数量
# 创建数据加载器
train_loader = DataLoader(dataset=train_data_full, batch_size=train_bs, shuffle=True, num_workers=workers) # 训练数据加载器
valid_loader = DataLoader(dataset=valid_data, batch_size=train_bs, num_workers=workers) # 验证数据加载器
print(next(iter(train_data_full)))
3. 模型搭建
3.1 模型定义
import timm
import torch
import torch.nn as nn
#### !!!timm库分类模型加载与分类头修改(timm.create_model/)
###########################################考试押题这里开始
# 模型加载和分类头修改
data_dir = os.getcwd() # 获取当前工作目录
path_resnet18 = os.path.join(data_dir, "data", "pretrained_model", "resnet18-5c106cde.pth") # ResNet18预训练权重路径
model = timm.create_model('resnet18', checkpoint_path=path_resnet18) # 创建ResNet18模型并加载预训练权重
num_ftrs = model.fc.in_features # 获取全连接层的输入特征数
model.fc = nn.Linear(num_ftrs, 265) # 修改分类头为265类输出
###########################################考试押题这里结束
4. 损失函数与优化器
#### !!!完成循环训练(必考CosineAnnealingLR/CrossEntropyLoss/SGD/Parameters)
###########################################考试押题这里开始
import torch.optim as optim # 导入优化器模块
from torch.optim.lr_scheduler import CosineAnnealingLR # 导入余弦退火学习率调度器
lr_init = 0.01 # 初始学习率
max_epoch = 5 # 最大训练轮数
loss_f = nn.CrossEntropyLoss() # 交叉熵损失函数
optimizer = optim.SGD(model.parameters(), lr=lr_init, momentum=0.9, weight_decay=1e-4) # SGD优化器
scheduler = CosineAnnealingLR(optimizer, T_max=max_epoch, eta_min=lr_init*0.01) # 余弦退火学习率调度器
###########################################考试押题这里结束
5. 迭代训练
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix # 导入评估指标
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 设置设备(GPU或CPU)
model.to(device) # 将模型移动到指定设备
for epoch_idx in range(max_epoch): # 训练循环
print(f"epoch:{epoch_idx}/{max_epoch}") # 打印当前轮数
# 训练阶段
all_preds = [] # 存储所有预测结果
all_labels = [] # 存储所有真实标签
train_loss = [] # 存储训练损失
for i, data in tqdm(enumerate(train_loader), desc='training: '): # 遍历训练数据
inputs, labels, path_imgs = data # 解包数据
inputs, labels = inputs.to(device), labels.to(device) # 将数据移动到设备
print("inputs:{}".format(inputs.shape)) # 打印输入张量形状
print("labels:{}".format(labels.shape)) # 打印标签张量形状
####################################################################考试押题这里开始
# 前向传播
outputs = model(inputs) # 模型前向传播,输出形状为[64, 265]
print("outputs:{}".format(outputs.shape)) # 打印输出张量形状
optimizer.zero_grad() # 清空梯度
_, predicted = torch.max(outputs.data, 1) # 获取预测类别
loss = loss_f(outputs.cpu(), labels.cpu()) # 计算损失
all_preds.extend(predicted.cpu().numpy()) # 收集预测结果
all_labels.extend(labels.cpu().numpy()) # 收集真实标签
loss.backward() # 反向传播
optimizer.step() # 更新参数
train_loss.append(loss.item()) # 记录损失值
break # 只训练一个批次就退出(用于调试)
####################################################################考试押题这里结束
# 计算训练指标
acc = accuracy_score(all_labels, all_preds) # 计算准确率
cr = classification_report(all_labels, all_preds) # 生成分类报告
conf_mx = confusion_matrix(all_labels, all_preds) # 生成混淆矩阵
print("train acc:{:.2f}".format(np.mean(acc))) # 打印训练准确率
print(cr) # 打印分类报告
print(conf_mx) # 打印混淆矩阵
break # 只训练一个epoch就退出(用于调试)
# 验证阶段(被break跳过)
valid_loss = []
for i, data in tqdm(enumerate(valid_loader), desc='valiate: '):
inputs, labels, path_imgs = data
inputs, labels = inputs.to(device), labels.to(device)
# 前向传播
outputs = model(inputs)
loss = loss_f(outputs.cpu(), labels.cpu())
valid_loss.append(loss.item())
# 学习率调度器更新
scheduler.step() # 每个epoch之后调用一次
print("train loss:{:.2f}, valid loss:{:.2f}".format(np.mean(train_loss), np.mean(valid_loss)))
####################################################################也有可能考试押题这里结束
#(create_model)
####################################################################考试押题这里开始
# 加载预训练模型进行预测
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, classification_report, accuracy_score
mapping_dict = valid_data.map_dict # 获取265类映射为4类的字典
device = "cpu" # 设置设备为CPU
data_dir = os.getcwd() # 获取当前工作目录
path_ckpt = os.path.join(data_dir, "data", "pretrained_model", "resnet18-5c106cde.pth") # 预训练权重路径
model = timm.create_model('resnet18', checkpoint_path=path_resnet18) # 创建模型
ckpt = torch.load(path_ckpt, map_location=device, weights_only=True) # 加载预训练权重
model.load_state_dict(ckpt) # 加载预训练模型权重
num_ftrs = model.fc.in_features # 获取全连接层输入特征数
model.fc = nn.Linear(num_ftrs, 265) # 修改分类头为265类
model.to(device) # 将模型移动到设备
model.eval() # 设置为评估模式
####################################################################考试押题这里结束
# 推理
all_preds = [] # 存储所有预测结果
all_labels = [] # 存储所有真实标签
for i, data in tqdm(enumerate(valid_loader), total=len(valid_loader)): # 遍历验证数据
inputs, labels, path_imgs = data # 解包数据
inputs, labels = inputs.to(device), labels.to(device) # 将数据移动到设备
outputs = model(inputs) # 模型前向传播
# 记录结果
_, predicted = torch.max(outputs.data, 1) # 获取预测类别
all_preds.extend(predicted.cpu().numpy()) # 收集预测结果
all_labels.extend(labels.cpu().numpy()) # 收集真实标签
#### !!!265类分类模型迁移到4分类的标签映射中
####################################################################考试押题这里开始
# 计算265分类的评估指标
acc = accuracy_score(all_labels, all_preds) # 计算准确率
cr = classification_report(all_labels, all_preds) # 生成分类报告
conf_mx = confusion_matrix(all_labels, all_preds) # 生成混淆矩阵
print("val acc:{:.2f}".format(np.mean(acc))) # 打印验证准确率
print(cr) # 打印分类报告
print(conf_mx) # 打印混淆矩阵
#### !!!混淆矩阵统计与指标分析
# 统计结果 - 将265类映射到4类进行评估
class_names_4 = "厨余垃圾 可回收垃圾 其他垃圾 有害垃圾".split() # 4分类的类别名称
all_labels_4 = [mapping dict[i] for i in all labels] # 将265类预测结果映射到4类
all_preds_4 = [mapping_dict[i] for i in all_preds]
report = classification_report(all_labels_4, all_preds_4, target_names=class_names_4, digits=4) # 生成4分类报告
print("\n各类别评估指标:") # 打印标题
print(report) # 打印4分类评估报告
####################################################################考试押题这里结束
机器学习################################################################################################
# 二、监督学习
1.分类任务
import pandas as pd # 导入pandas库,用于数据处理和分析
1. 数据加载与预处理
# 读取训练数据和测试数据
train = pd.read_csv('20250719机器学习编程实操代码-02信用评分卡(王贺)/train.csv') # 加载训练数据集
test = pd.read_csv('20250719机器学习编程实操代码-02信用评分卡(王贺)/test.csv') # 加载测试数据集
2.处理缺失值 - 只对分类特征进行简单填充
# 将Credit_Product列的缺失值填充为'Unknown'
train['Credit_Product'] = train['Credit_Product'].fillna('Unknown') # 用'Unknown'填充训练集中Credit_Product列的缺失值
test['Credit_Product'] = test['Credit_Product'].fillna('Unknown') # 用'Unknown'填充测试集中Credit_Product列的缺失值
# 分离特征和目标变量
# 训练数据中删除ID列和Target列作为特征
X_train = train.drop(['ID', 'Target'], axis=1) # 从训练数据中删除ID列和Target列,保留特征列
# 提取目标变量(客户是否为好客户)
y_train = train['Target'] # 提取训练数据中的目标变量(标签)
3. 特征预处理
# 定义分类特征和数值特征
# 分类特征列表
cat_cols = ['Gender', 'Region_Code', 'Occupation', 'Channel_Code', 'Credit_Product', 'Is_Active']
# 数值特征列表
num_cols = ['Age', 'Vintage', 'Avg_Account_Balance']
label = 'Target'
features = cat_cols+num_cols
##(sklearn/CatBoost/LGBM/XGBBoost)
##(onehot/Classifier)
from sklearn.model_selection import train_test_split, StratifiedKFold, KFold, RepeatedKFold # 导入数据分割和交叉验证相关函数
from sklearn.linear_model import LogisticRegression # 导入逻辑回归模型
from sklearn.tree import DecisionTreeClassifier # 导入决策树分类器
from sklearn.ensemble import RandomForestClassifier # 导入随机森林分类器
from xgboost import XGBClassifier # 导入XGBoost分类器
from lightgbm import LGBMClassifier # 导入LightGBM分类器
from catboost import CatBoostClassifier # 导入CatBoost分类器
from sklearn.metrics import accuracy_score, roc_auc_score, f1_score, classification_report, confusion_matrix # 导入评估指标
from sklearn.datasets import make_classification # 导入生成分类数据的函数
from sklearn.preprocessing import StandardScaler, OneHotEncoder # 导入数据预处理工具
import matplotlib as plt # 导入matplotlib绘图库
import numpy as np # 导入numpy数值计算库
import time # 导入时间模块,用于计时
# 定义模型列表
models = { # 创建模型字典,包含各种机器学习算法
"Logistic Regression": LogisticRegression(), # 逻辑回归模型
"Decision Tree": DecisionTreeClassifier(random_state=42), # 决策树模型,设置随机种子
"Random Forest": RandomForestClassifier(random_state=42), # 随机森林模型,设置随机种子
"XGBoost": XGBClassifier( # XGBoost模型,设置超参数
n_estimators=30, # 树的数量
max_depth=6, # 树的最大深度
learning_rate=0.05, # 学习率
subsample=0.8, # 样本采样比例
colsample_bytree=0.8, # 特征采样比例
random_state=42, # 随机种子
n_jobs=-1, # 使用所有CPU核心
early_stopping_round=10#需要的话就写上
),
"LightGBM": LGBMClassifier( # LightGBM模型,设置超参数
n_estimators=30, # 树的数量
max_depth=6, # 树的最大深度
learning_rate=0.05, # 学习率
subsample=0.8, # 样本采样比例
colsample_bytree=0.8, # 特征采样比例
random_state=42, # 随机种子
n_jobs=-1 # 使用所有CPU核心
),
"CatBoost": CatBoostClassifier( # CatBoost模型,设置超参数
iterations=30, # 迭代次数
depth=6, # 树的最大深度
learning_rate=0.05, # 学习率
random_seed=42, # 随机种子
verbose=0, # 不显示训练过程
thread_count=-1 # 使用所有CPU核心
)
}
# 模型性能记录
results = { # 创建结果字典,用于存储各模型的性能指标
"Model": [], # 模型名称列表
"AUC": [], # AUC分数列表
"Time (s)": [], # 训练时间列表
"Parameters": [] # 模型参数列表
}
# 假设你已经分离了特征和标签
results = {"Model": [], "AUC": [], "F1": [], "ACC": [], "Time (s)": [], "Parameters": []} # 重新初始化结果字典,增加F1和ACC指标
kfold = KFold(n_splits=5, shuffle=True, random_state=42) # 创建5折交叉验证对象,打乱数据并设置随机种子
for name, model in models.items(): # 遍历每个模型
print(f"\nTraining {name}...") # 打印当前训练的模型名称
start_time = time.time() # 记录训练开始时间
# 用于收集每折的评估结果
auc_scores = [] # 存储每折的AUC分数
f1_scores = [] # 存储每折的F1分数
acc_scores = [] # 存储每折的准确率分数
y_true_all = [] # 存储所有真实标签
y_pred_all = [] # 存储所有预测标签
# �� K 折循环
for train_index, val_index in kfold.split(X_train): # 进行5折交叉验证
# === 1. 划分数据 ===
X_train_fold = X_train.iloc[train_index] # 获取当前折的训练特征
X_val_fold = X_train.iloc[val_index] # 获取当前折的验证特征
y_train_fold = y_train.iloc[train_index] # 获取当前折的训练标签
y_val_fold = y_train.iloc[val_index] # 获取当前折的验证标签
# === 2. 数值特征标准化(在当前训练折上 fit)===
scaler = StandardScaler() # 创建标准化器
X_train_num = scaler.fit_transform(X_train_fold[num_cols]) # 对训练集数值特征进行标准化
X_val_num = scaler.transform(X_val_fold[num_cols]) # 用训练折的参数对验证集数值特征进行标准化
# === 3. 分类特征独热编码(在当前训练折上 fit)===
encoder = OneHotEncoder(handle_unknown='ignore', sparse_output=False) # 创建独热编码器,忽略未知类别,输出密集矩阵
X_train_cat = encoder.fit_transform(X_train_fold[cat_cols]) # 对训练集分类特征进行独热编码
X_val_cat = encoder.transform(X_val_fold[cat_cols]) # 用训练折的参数对验证集分类特征进行独热编码
# === 4. 合并特征 ===
X_train_processed = np.hstack([X_train_num, X_train_cat]) # 水平堆叠数值特征和编码后的分类特征
X_val_processed = np.hstack([X_val_num, X_val_cat]) # 水平堆叠数值特征和编码后的分类特征
# === 5. 训练模型(建议 clone,防止状态污染)===
# from sklearn.base import clone
# model_clone = clone(model)
# model_clone.fit(X_train_processed, y_train_fold)
model.fit(X_train_processed, y_train_fold) # 训练模型
# === 6. 预测 ===
y_pred_proba = model.predict_proba(X_val_processed)[:, 1] # 获取正类的预测概率
y_pred = model.predict(X_val_processed) # 获取预测标签
# === 7. 计算指标 ===
auc_scores.append(roc_auc_score(y_val_fold, y_pred_proba)) # 计算AUC分数并添加到列表
f1_scores.append(f1_score(y_val_fold, y_pred)) # 计算F1分数并添加到列表
acc_scores.append(accuracy_score(y_val_fold, y_pred)) # 计算准确率并添加到列表
y_true_all.extend(y_val_fold) # 将真实标签添加到总列表
y_pred_all.extend(y_pred) # 将预测标签添加到总列表
# === 8. 汇总 K 折结果 ===
auc = np.mean(auc_scores) # 计算平均AUC分数
f1 = np.mean(f1_scores) # 计算平均F1分数
acc = np.mean(acc_scores) # 计算平均准确率
cr = classification_report(y_true_all, y_pred_all) # 生成分类报告
conf_mx = confusion_matrix(y_true_all, y_pred_all) # 生成混淆矩阵
total_time = time.time() - start_time # 计算总训练时间
# === 9. 存储结果(每个模型只记录一次)===
results["Model"].append(name) # 添加模型名称到结果字典
results["AUC"].append(auc) # 添加AUC分数到结果字典
results["F1"].append(f1) # 添加F1分数到结果字典
results["ACC"].append(acc) # 添加准确率到结果字典
results["Time (s)"].append(total_time) # 添加训练时间到结果字典
results["Parameters"].append(str(model.get_params())) # 添加模型参数到结果字典
# === 10. 打印结果 ===
print(f"{name} - AUC: {auc:.4f}, F1: {f1:.4f}, ACC: {acc:.4f}, Time: {total_time:.2f}s") # 打印模型性能指标
print("\n分类报告:") # 打印分类报告标题
print(cr) # 打印详细的分类报告
print("\n混淆矩阵:") # 打印混淆矩阵标题
print(conf_mx) # 打印混淆矩阵
Stacking
from sklearn.ensemble import StackingClassifier # 导入堆叠分类器
from sklearn.linear_model import LogisticRegression # 导入逻辑回归作为元模型
# 定义基模型
base_models = [ # 创建基础模型列表
('xgb', xgb.XGBClassifier(n_estimators=30, max_depth=5, learning_rate=0.05, random_state=42)), # XGBoost基础模型
('lgb', lgb.LGBMClassifier(n_estimators=30, max_depth=5, learning_rate=0.05, random_state=42, verbose=-1)), # LightGBM基础模型
('cat', cb.CatBoostClassifier(iterations=30, depth=5, learning_rate=0.05, random_seed=42, verbose=0)) # CatBoost基础模型
]
# 定义元模型
meta_model = LogisticRegression(C=0.1, max_iter=1000, random_state=42) # 创建逻辑回归元模型
# 创建Stacking分类器
stacking_model = StackingClassifier( # 创建堆叠分类器
estimators=base_models, # 基础模型列表
final_estimator=meta_model, # 元模型
cv=5, # 5折交叉验证
n_jobs=12 # 使用12个CPU核心
)
# 训练Stacking模型
print("Training Stacking Model...") # 打印训练开始信息
start_time = time.time() # 记录训练开始时间
stacking_model.fit(X_train_final, y_train) # 训练堆叠模型
train_time = time.time() - start_time # 计算训练时间
# 验证集评估
y_pred_stacking = stacking_model.predict_proba(X_val_final)[:, 1] # 获取堆叠模型在验证集上的预测概率
auc_stacking = roc_auc_score(y_val, y_pred_stacking) # 计算堆叠模型的AUC分数
print(f"Stacking融合AUC: {auc_stacking:.4f}, Time: {train_time:.2f}s") # 打印堆叠模型的性能指标
from sklearn.linear_model import ElasticNet, BayesianRidge # 导入弹性网络和贝叶斯岭回归模型
def stack_model(oof_1, oof_2, oof_3, predictions_1, predictions_2, predictions_3, y, eval_type='regression'):
"""
使用堆叠集成方法组合多个模型的预测结果
参数:
oof_1, oof_2, oof_3 -- 三个基础模型在训练集上的袋外预测(out-of-fold predictions)
predictions_1, predictions_2, predictions_3 -- 三个基础模型在测试集上的预测
y -- 训练集的真实目标值
eval_type -- 问题类型:'regression'(默认) 或 'binary'
返回:
oof -- 第二层模型在训练集上的预测
predictions -- 第二层模型在测试集上的预测
"""
# 将训练集和测试集的预测结果水平堆叠
# 最终形状: [n_samples, n_models]
train_stack = np.vstack([oof_1, oof_2, oof_3]).transpose() # 将三个模型的OOF预测垂直堆叠后转置
# print(train_stack)
test_stack = np.vstack([predictions_1, predictions_2, predictions_3]).transpose() # 将三个模型的测试集预测垂直堆叠后转置
# 导入交叉验证方法
from sklearn.model_selection import RepeatedKFold # 导入重复K折交叉验证
# 初始化交叉验证: 5折,不重复(n_repeats=1),固定随机种子
folds = RepeatedKFold(n_splits=5, n_repeats=1, random_state=2018) # 创建5折交叉验证对象
# 初始化存储结果的数组
oof = np.zeros(train_stack.shape[0]) # 初始化训练集OOF预测数组
predictions = np.zeros(test_stack.shape[0]) # 初始化测试集预测数组
# 获取交叉验证的实际折数 (5折x1次重复=5)
n_splits = folds.get_n_splits() # 获取折数
n_repeats = folds.n_repeats # 获取重复次数
# 进行交叉验证训练
for fold_, (trn_idx, val_idx) in enumerate(folds.split(train_stack, y)): # 遍历每一折
print("fold n°{}".format(fold_+1)) # 打印当前折数
# 划分当前折的训练/验证数据
trn_data, trn_y = train_stack[trn_idx], y[trn_idx] # 获取当前折的训练数据
val_data, val_y = train_stack[val_idx], y[val_idx] # 获取当前折的验证数据
print("-" * 10 + "Stacking " + str(fold_+1) + "-" * 10) # 打印分隔线
# 初始化第二层模型 (贝叶斯岭回归)
clf = BayesianRidge() # 创建贝叶斯岭回归模型
# 在训练数据上拟合第二层模型
clf.fit(trn_data, trn_y) # 训练第二层模型
# 预测验证集并保存结果
oof[val_idx] = clf.predict(val_data) # 预测验证集并保存到OOF数组
# 预测测试集并累加 (后续取平均)
predictions += clf.predict(test_stack) / (n_splits * n_repeats) # 预测测试集并累加,最后取平均
# 根据问题类型评估性能
if eval_type == 'regression': # 如果是回归问题
from sklearn.metrics import mean_squared_error # 导入均方误差
# 回归问题: 计算均方根误差(RMSE)
print('RMSE: ', np.sqrt(mean_squared_error(y, oof))) # 计算并打印RMSE
elif eval_type == 'binary': # 如果是二分类问题
from sklearn.metrics import log_loss # 导入对数损失
# 二分类问题: 计算对数损失
print('Log Loss: ', log_loss(y, oof)) # 计算并打印对数损失
return oof, predictions # 返回OOF预测和测试集预测