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` 参数与原代码一致
阅读全文

相关推荐