import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import LabelEncoder from sklearn.neighbors import KNeighborsRegressor # 修改1:导入KNN回归器 from sklearn.metrics import mean_squared_error, r2_score from scipy.optimize import minimize import matplotlib.pyplot as plt from pathlib import Path from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import RandomizedSearchCV from sklearn.metrics import make_scorer, f1_score excel_path = Path(r"C:\Users\Administrator\Desktop\掺氨比、燃尽风位置、主燃区温度\标准化扩展数据_自助采样.xlsx") data = pd.read_excel(excel_path, sheet_name='Sheet1') # 数据预处理 cat_cols = [] le = LabelEncoder() X = data[['掺氨比', '燃尽风位置', '主燃区温度']] y = data['NO排放浓度'] # 数据集划分 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) # 网格搜索参数设置 from sklearn.model_selection import GridSearchCV param_grid = { 'n_neighbors': list(range(2,10,1)), # 扩展K值范围 'weights': ['uniform', 'distance'], 'p': [1, 2, 3], # 增加p=3 } knn_base = KNeighborsRegressor() # 修改3:创建KNN基础模型 grid_search = GridSearchCV( estimator=knn_base, param_grid=param_grid, scoring='neg_mean_squared_error', cv=5, n_jobs=-1, verbose=2 ) # 模型训练 grid_search.fit(X_train, y_train) knn = grid_search.best_estimator_ # 性能评估 print(f"交叉验证平均MSE: {-grid_search.best_score_:.2f}") y_pred = knn.predict(X_test) print("\n模型性能评估:") print(f"MSE: {mean_squared_error(y_test, y_pred):.2f}") print(f"RMSE: {mean_squared_error(y_test, y_pred, squared=False):.2f}") print(f"R²: {r2_score(y_test, y_pred):.2%}") # 参数优化配置 param_constraints = { '掺氨比': (min(X['掺氨比']), max(X['掺氨比'])), '燃尽风位置': (min(X['燃尽风位置']), max(X['燃尽风位置'])), '主燃区温度': (min(X['主燃区温度']), max(X['主燃区温度'])) } def predict_no_emission(params): """包装预测函数""" input_df = pd.DataFrame([params], columns=X.columns) return knn.predict(input_df)[0] # 修改4:使用KNN进行预测 # 执行优化 initial_guess = X.median().values bounds = list(param_constraints.values()) result = minimize( fun=predict_no_emission, x0=initial_guess, method='SLSQP', bounds=bounds, options={'maxiter': 500, 'ftol': 1e-8} ) # 输出结果 optimized_params = result.x.copy() print("\n最优参数组合:") print(f"最小NO排放浓度预测值:{result.fun:.2f} mg/m³") print(f"掺氨比:{optimized_params[0]:.3f}") print(f"燃尽风位置:{optimized_params[1]}") print(f"主燃区温度:{optimized_params[2]:.0f}℃") 如何添加k折交叉验证
时间: 2025-07-08 17:35:37 浏览: 9
<think>好的,我现在需要帮助用户在已有的机器学习代码中添加K折交叉验证。用户当前的代码已经使用了网格搜索(GridSearchCV)进行超参数调优,并在测试集上评估了模型性能。现在用户想要添加K折交叉验证,我需要先理解他们当前代码的结构,然后找到合适的位置插入K折交叉验证。
首先,我需要确认用户的需求。用户可能希望使用K折交叉验证来更稳健地评估模型性能,而不仅仅是依赖一次的训练-测试分割。当前的代码中,GridSearchCV本身已经使用了交叉验证(cv=5),这可能是在参数调优阶段进行的。但用户可能还希望在整体流程中使用K折来验证模型的泛化能力,或者在调优之后再进行独立的交叉验证评估。
接下来,我需要查看用户当前的代码流程。他们导入了必要的库,读取数据,进行预处理,划分训练集和测试集,然后进行网格搜索来找到最佳参数的KNN回归模型。之后在测试集上评估性能,并进行参数优化寻找最小NO排放的参数组合。
用户现在希望添加K折交叉验证。可能的做法有两种:一是在网格搜索之外独立进行K折交叉验证,以评估模型的平均性能;二是调整现有的流程,将整个数据分为K折,并在每一折上进行训练和验证,包括参数调优。不过,考虑到GridSearchCV本身已经使用了交叉验证,可能用户需要的是在更外层的循环中使用K折,或者是在调优后对模型进行交叉验证评估。
首先,我需要确定用户是想在参数调优阶段之外使用K折,还是在模型评估阶段使用。因为GridSearchCV的cv参数已经设置为5,意味着在参数搜索时已经进行了5折交叉验证。但用户可能在调优后,希望用K折来验证最佳模型的稳定性,或者是在整个数据集上进行交叉验证,而不仅仅是单次的测试集评估。
例如,用户可能在调优后,使用K折交叉验证来多次训练和验证模型,得到更可靠的性能指标,如平均MSE、R²等。这可以通过使用cross_val_score或cross_validate函数来实现。或者,用户可能希望将整个数据分为K折,每次用其中K-1折训练,并在剩余一折测试,然后聚合结果。
现在,用户当前的测试集是固定的,通过train_test_split划分的。如果添加K折交叉验证,可能需要将整个数据集分成K折,然后在每一折上进行训练和测试,从而减少对单次划分的依赖,得到更稳定的评估结果。
可能的步骤是:
1. 导入KFold或交叉验证相关的类。
2. 使用交叉验证方法替代或补充现有的train_test_split。
3. 在交叉验证循环中,进行模型训练、调优和评估。
4. 汇总各折的性能指标,计算平均值和标准差。
但用户的具体需求可能有所不同。例如,他们可能希望在调优后的最佳模型上应用交叉验证,以评估其泛化性能。或者在调优过程中已经使用了交叉验证,但希望在整个数据集上进行交叉验证,包括调优步骤。
现在,查看用户当前的代码,GridSearchCV已经设置了cv=5,所以在参数调优时已经进行了5折交叉验证。而他们现在可能希望在整个流程中应用K折,例如,在每次交叉验证的折中进行参数调优,然后评估模型,最后得到综合结果。或者,他们可能希望在调优完成后,使用交叉验证来评估最佳模型的性能。
假设用户希望在整个数据集上进行K折交叉验证,而不是仅仅一次划分训练集和测试集。例如,使用5折交叉验证,每次划分不同的训练集和测试集,然后在每个训练集上进行网格搜索找到最佳模型,再在对应的测试集上评估性能,最后综合所有折的结果。
或者,用户可能希望在调优后的最佳模型上执行交叉验证,以评估其平均性能。例如,使用cross_val_score函数,传入最佳模型,然后进行交叉验证。
需要进一步分析这两种情况的区别:
- GridSearchCV内部的交叉验证:用于选择最佳超参数,每个参数组合在训练数据上进行K折交叉验证,选择平均表现最好的参数。
- 外部的交叉验证:可能用于评估整个流程(包括调优)的泛化性能。例如,将数据分为K折,每次用K-1折作为训练数据,进行网格搜索调优,然后在剩下的1折测试。这样可以得到K次测试结果,反映模型在调优后的泛化能力。这种方法称为嵌套交叉验证,其中外层是评估模型性能,内层是参数调优。
根据用户的问题描述,他们可能想要在现有流程中添加这样的外层交叉验证,或者只是简单地添加交叉验证来评估模型。需要确定用户的具体目标。
现在,用户当前的流程是:划分训练测试集 → 调优 → 评估测试集。现在他们想添加K折,可能希望用交叉验证替代单次的训练测试划分,或者补充现有的评估方式。
可能的解决方案:
方案一:使用交叉验证代替train_test_split
即,不使用train_test_split,而是使用KFold,循环每个折叠,进行训练和测试,并收集每次的评估结果。例如:
kf = KFold(n_splits=5)
mse_scores = []
r2_scores = []
for train_index, test_index in kf.split(X):
X_train, X_test = X.iloc[train_index], X.iloc[test_index]
y_train, y_test = y.iloc[train_index], y.iloc[test_index]
# 进行网格搜索训练
grid_search.fit(X_train, y_train)
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
mse_scores.append(mse)
r2_scores.append(r2)
然后计算平均的MSE和R²。
方案二:在调优后使用交叉验证评估模型
即在找到最佳模型后,使用cross_val_score来评估模型在整个数据上的交叉验证性能。
例如:
from sklearn.model_selection import cross_val_score
best_knn = grid_search.best_estimator_
cv_scores = cross_val_score(best_knn, X, y, cv=5, scoring='neg_mean_squared_error')
cv_mse = -cv_scores.mean()
这样可以得到模型在交叉验证下的平均MSE。
两种方案的区别在于,方案一是在每次交叉验证中重新进行网格搜索调优,而方案二是在整个数据集调优后,再在交叉验证中评估该模型。但方案二可能存在数据泄漏的问题,因为调优时可能已经用了整个数据的信息,所以正确的做法应该是方案一,即外层交叉验证划分训练测试,每次在训练集上调优,测试集上评估。
不过,用户当前的代码已经有一个测试集,并且调优是在训练集上进行的,这可能已经避免了数据泄漏。如果用户想要更稳健的评估,可能需要使用外层交叉验证。
现在用户的问题是如何添加K折交叉验证,需要明确他们想在哪里添加。例如,他们可能希望用K折来替代现有的测试集评估,或者补充现有流程。
另外,用户当前的优化部分(使用scipy.optimize.minimize)可能也需要考虑交叉验证的结果,但可能这部分不需要改变,因为优化部分是基于训练好的模型进行的。
现在,根据用户的代码结构,可能的添加步骤是:
1. 导入KFold或其他交叉验证相关模块。
2. 在数据预处理后,使用KFold进行交叉验证循环。
3. 在每个循环中,划分训练集和验证集(或测试集),进行网格搜索调优,训练模型,评估性能。
4. 收集各折的性能指标,最后输出平均值和标准差。
或者,如果用户希望保持现有的测试集,并且想在训练集上使用交叉验证进行模型调优和验证,那么可能需要调整参数调优的方式,但用户可能已经通过GridSearchCV完成了这一点。
可能用户当前的代码已经进行了交叉验证(在GridSearchCV中cv=5),但他们的测试集评估只是单次的。他们可能希望得到更稳健的测试结果,所以想用K折来多次评估。
但此时,正确的方法是使用嵌套交叉验证:外层循环将数据分为训练和测试集,内层循环在训练集上进行网格搜索的交叉验证。但这样会消耗较多计算资源。
不过,根据用户的问题,他们可能只需要在现有的流程中添加交叉验证,可能是在模型评估部分。例如,在得到最佳模型后,进行交叉验证评估,而不仅仅是测试集的评估。
或者,他们可能希望替换现有的单次测试集评估,改为K折交叉验证的平均结果。
现在,我需要根据这些可能性,给出具体的步骤。
例如,在用户代码中,他们现在使用train_test_split划分了训练集和测试集,然后进行网格搜索,并在测试集上评估。现在,他们可能希望使用K折交叉验证来评估模型的性能,而不是仅仅依赖一次测试集的结果。
在这种情况下,可以添加如下步骤:
在模型训练和调优之后,使用cross_val_score或cross_validate来对最佳模型进行交叉验证评估。
例如:
在代码的“性能评估”部分之后,添加:
from sklearn.model_selection import cross_val_score
# 使用最佳模型进行交叉验证
cv_scores = cross_val_score(knn, X, y, cv=5, scoring='neg_mean_squared_error')
cv_mse = -cv_scores.mean()
cv_rmse = np.sqrt(-cv_scores).mean()
print(f"交叉验证平均MSE: {cv_mse:.2f}")
print(f"交叉验证平均RMSE: {cv_rmse:.2f}")
但这里需要注意,cross_val_score的scoring参数是否与用户的需求一致。例如,用户可能需要同时收集MSE和R²的值,此时可以使用cross_validate函数。
此外,用户当前的代码在网格搜索后已经打印了交叉验证的平均MSE(通过grid_search.best_score_),这可能是在训练集上的交叉验证结果。如果用户希望在整个数据集上进行交叉验证,可能需要重新评估。
但需要明确,grid_search.best_score_是网格搜索过程中内部交叉验证的最佳得分(即平均验证分数),而用户现在可能希望进行另一种交叉验证。
或者,用户可能希望执行更严格的交叉验证,例如,在未进行初始train_test_split的情况下,直接对整个数据集进行交叉验证,每次划分不同的训练测试集,并进行网格搜索和评估。
此时,正确的做法是移除train_test_split,使用KFold进行循环,每次划分不同的训练和测试集,然后在训练集上进行网格搜索调优,在测试集上评估。
例如:
kf = KFold(n_splits=5, shuffle=True, random_state=42)
mse_scores = []
r2_scores = []
for train_index, test_index in kf.split(X):
X_train, X_test = X.iloc[train_index], X.iloc[test_index]
y_train, y_test = y.iloc[train_index], y.iloc[test_index]
# 网格搜索调优
grid_search = GridSearchCV(estimator=knn_base, param_grid=param_grid, ...)
grid_search.fit(X_train, y_train)
best_model = grid_search.best_estimator_
# 在测试集评估
y_pred = best_model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
mse_scores.append(mse)
r2_scores.append(r2)
print(f"平均MSE: {np.mean(mse_scores):.2f} ± {np.std(mse_scores):.2f}")
print(f"平均R²: {np.mean(r2_scores):.2%} ± {np.std(r2_scores):.2%}")
这种方式将整个数据集分成K折,每次用其中K-1折进行调优和训练,1折测试,最后得到K次测试结果的平均。这样能更全面地评估模型的泛化能力,避免单次划分的偶然性。
但这样会增加计算时间,因为每次循环都要重新进行网格搜索。不过对于用户的需求来说,这可能是更严谨的做法。
总结,用户需要在代码中添加K折交叉验证的循环,可能在模型调优和评估阶段,替代现有的单次划分方式,或者补充现有的测试集评估。根据用户的问题,可能的解决方案是使用KFold循环,每次进行网格搜索和评估,并收集结果。</think>---
### **K折交叉验证添加步骤**
在您的代码中添加K折交叉验证可以更全面地评估模型性能。以下是具体实现方案:
---
#### **1. 导入KFold模块**
在代码开头添加:
```python
from sklearn.model_selection import KFold
```
---
#### **2. 定义K折参数并初始化评估指标容器**
在数据集划分前添加:
```python
# 定义K折参数(例如5折)
k = 5
kf = KFold(n_splits=k, shuffle=True, random_state=42)
# 初始化指标容器
mse_scores = []
rmse_scores = []
r2_scores = []
```
---
#### **3. 重构代码为K折循环**
替换原有的 `train_test_split` 和后续流程,改为循环处理每个折:
```python
for train_index, test_index in kf.split(X):
# 划分训练集和测试集
X_train, X_test = X.iloc[train_index], X.iloc[test_index]
y_train, y_test = y.iloc[train_index], y.iloc[test_index]
# 网格搜索调参
grid_search = GridSearchCV(
estimator=knn_base,
param_grid=param_grid,
scoring='neg_mean_squared_error',
cv=5, # 内层交叉验证(参数优化用)
n_jobs=-1,
verbose=0
)
grid_search.fit(X_train, y_train)
best_model = grid_search.best_estimator_
# 预测与评估
y_pred = best_model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, y_pred)
# 记录指标
mse_scores.append(mse)
rmse_scores.append(rmse)
r2_scores.append(r2)
```
---
#### **4. 输出K折验证结果**
在循环结束后添加统计结果:
```python
print("\nK折交叉验证综合结果:")
print(f"平均MSE: {np.mean(mse_scores):.2f} ± {np.std(mse_scores):.2f}")
print(f"平均RMSE: {np.mean(rmse_scores):.2f} ± {np.std(rmse_scores):.2f}")
print(f"平均R²: {np.mean(r2_scores):.2%} ± {np.std(r2_scores):.2%}")
```
---
### **完整代码修改对比**
#### **原代码片段**
```python
# 数据集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 网格搜索与训练
grid_search.fit(X_train, y_train)
knn = grid_search.best_estimator_
# 测试集评估
y_pred = knn.predict(X_test)
print(f"MSE: {mean_squared_error(y_test, y_pred):.2f}")
```
#### **修改后代码片段**
```python
# K折交叉验证
k = 5
kf = KFold(n_splits=k, shuffle=True, random_state=42)
mse_scores = []
rmse_scores = []
r2_scores = []
for train_index, test_index in kf.split(X):
X_train, X_test = X.iloc[train_index], X.iloc[test_index]
y_train, y_test = y.iloc[train_index], y.iloc[test_index]
grid_search = GridSearchCV(...) # 保持原有参数
grid_search.fit(X_train, y_train)
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, y_pred)
mse_scores.append(mse)
rmse_scores.append(rmse)
r2_scores.append(r2)
# 输出综合评估结果
print("\nK折交叉验证综合结果:")
print(f"平均MSE: {np.mean(mse_scores):.2f} ± {np.std(mse_scores):.2f}")
print(f"平均RMSE: {np.mean(rmse_scores):.2f} ± {np.std(rmse_scores):.2f}")
print(f"平均R²: {np.mean(r2_scores):.2%} ± {np.std(r2_scores):.2%}")
```
---
### **关键改进说明**
1. **双重交叉验证**:
- **外层K折**:评估模型泛化性能
- **内层GridSearchCV**:优化超参数(原代码中的 `cv=5`)
2. **结果稳定性**:
- 通过多折验证的均值和标准差,反映模型性能波动范围
- 例如:`平均MSE: 12.34 ± 1.23` 表示模型在5次验证中MSE在11.11~13.57之间波动
3. **数据利用效率**:
- 避免单次 `train_test_split` 的偶然性
- 所有数据均参与过训练和测试
---
### **注意事项**
- **计算资源**:K折交叉验证会增加 $k$ 倍计算时间,建议根据数据量调整 $k$ 值
- **随机性控制**:设置 `shuffle=True` 和 `random_state` 保证结果可复现
- **参数一致性**:确保每个折的 `GridSearchCV` 参数与原代码一致
阅读全文
相关推荐






