已设置Random State的模型执行K折交叉验证时结果仍不一致的原因及解决方案
我来帮你梳理下问题所在,以及对应的解决方法:
问题背景
你已经为每个模型都设置了random_state=42,初始化代码如下:
def intiailize_clf_models(self): model = RandomForestClassifier(random_state=42) self.clf_models.append((model)) model = ExtraTreesClassifier(random_state=42) self.clf_models.append((model)) model = MLPClassifier(random_state=42) self.clf_models.append((model)) model = LogisticRegression(random_state=42) self.clf_models.append((model)) model = xgb.XGBClassifier(random_state=42) self.clf_models.append((model)) model = lgb.LGBMClassifier(random_state=42) self.clf_models.append((model))
随后用4折交叉验证评估模型:
def kfold_cross_validation(self): clf_models = self.get_models() models = [] self.results = {} for model in clf_models: self.current_model_name = model.__class__.__name__ cross_validate = cross_val_score(model, self.xtrain, self.ytrain, cv=4) self.mean_cross_validation_score = cross_validate.mean() print("Kfold cross validation for", self.current_model_name) self.results[self.current_model_name] = self.mean_cross_validation_score models.append(model)
但每次运行结果依然有差异,这确实挺让人困惑的,下面来分析原因和解决办法。
原因分析
结果不稳定的核心原因通常不是你设置的模型random_state没用,而是还有其他随机因素没被控制住,主要包括:
交叉验证折划分的随机性
当你用cross_val_score(cv=4)时,scikit-learn会根据任务类型(分类/回归)自动选择拆分器:分类用StratifiedKFold,回归用KFold。默认情况下这些拆分器是不打乱数据的,但如果你的数据集本身有随机顺序,或者你之前对数据做过无种子的打乱操作,就会导致拆分变化;另外如果你的代码中隐式启用了shuffle=True但没设random_state,那每次拆分都会完全不同。模型并行训练的随机性
像随机森林、XGBoost、LightGBM这类支持多线程/多进程训练的模型,当n_jobs(或nthread)大于1时,线程调度的顺序可能会影响随机数的生成流程,哪怕你设置了random_state。部分模型的并行实现中,随机数生成的同步机制不够完善,会导致最终拟合的模型有细微差异。部分模型的隐性随机操作
比如MLPClassifier,除了权重初始化的random_state,如果用了随机梯度下降优化器,批次样本的读取顺序(即使设置了random_state)在并行处理时也可能出现波动;还有一些模型的底层实现中,存在未被random_state完全覆盖的随机逻辑。
解决方法
要实现100%的结果复现,需要把所有可能的随机因素都固定住,按以下步骤操作:
1. 显式固定交叉验证拆分器的随机种子
不要再依赖cv=4的默认拆分,手动定义拆分器并设置random_state,确保每次折的划分完全一致:
from sklearn.model_selection import StratifiedKFold # 分类任务用这个,回归用KFold def kfold_cross_validation(self): clf_models = self.get_models() models = [] self.results = {} # 显式定义拆分器,固定随机种子 # 如果需要打乱数据,加上shuffle=True;不需要就去掉 cv = StratifiedKFold(n_splits=4, shuffle=True, random_state=42) for model in clf_models: self.current_model_name = model.__class__.__name__ # 使用自定义的cv拆分器 cross_validate = cross_val_score(model, self.xtrain, self.ytrain, cv=cv) self.mean_cross_validation_score = cross_validate.mean() print("Kfold cross validation for", self.current_model_name) self.results[self.current_model_name] = self.mean_cross_validation_score models.append(model)
2. 固定模型的所有随机相关参数
除了random_state,还要针对不同模型做额外设置:
- 树模型(RandomForest/ExtraTrees):如果用了
n_jobs>1仍有波动,可以临时将n_jobs=1(牺牲速度换稳定性),或者确认bootstrap等参数的随机行为被random_state覆盖。 - XGBoost:除了
random_state=42,再设置seed=42(冗余但保险),新版本中可以加deterministic=True强制确定性训练,同时设置nthread=1避免并行随机性。 - LightGBM:设置
random_state=42+deterministic=True+n_jobs=1,彻底消除并行带来的随机波动。 - MLPClassifier:如果用了
shuffle=True,确保依赖random_state固定批次顺序;或者设置shuffle=False,同时可以关闭early_stopping(如果启用了)避免提前停止的随机性。
3. 固定全局随机种子
在代码最开头设置全局种子,覆盖Python、NumPy等所有可能生成随机数的库:
import random import numpy as np # 固定全局随机种子 random.seed(42) np.random.seed(42) # 如果用了TensorFlow/PyTorch(比如MLP用这些后端),也要对应设置种子 # 比如TensorFlow: tf.random.set_seed(42) # PyTorch: torch.manual_seed(42); torch.cuda.manual_seed_all(42)
4. 确认模型克隆的一致性
scikit-learn的cross_val_score会自动克隆模型来拟合每个折,默认会复制所有参数(包括random_state),但如果你的模型是自定义的,要确认克隆逻辑没有丢失random_state参数。
内容的提问来源于stack exchange,提问作者Victor Uwaje

