为何Sklearn逻辑回归迭代训练与单次训练的评估指标有差异?
问题
我正在以迭代(逐epoch)方式训练sklearn的LogisticRegression,目的是借此获取每一轮训练后的均方误差(MSE),而这无法通过常规方式获取。但我发现,这种迭代训练方式得到的评估指标(如F1 score)与单次调用model.fit()训练得到的结果略有不同,想请教原因。
常规训练方式
model = LogisticRegression( random_state=42, C=.001, penalty='l1', max_iter=500, solver='saga') model.fit(X_train, y_train) # Predict Class labels pred_labels_val = model.predict(X_val) pred_labels_test = model.predict(X_test) # F1 Score f1_val = metrics.f1_score(y_val, pred_labels_val) f1_test = metrics.f1_score(y_test, pred_labels_test)
迭代训练方式
为验证结果一致性,我将迭代轮次设置为之前常规训练模型收敛时的轮次,其余参数保持一致,以此获取每一轮的MSE。
epochs = model.named_steps['logisticregression'].n_iter_[0] model = LogisticRegression( random_state=42, C=.001, penalty='l1', max_iter=1, # this forces training to run for only one epoch solver='saga') for epoch in range(epochs): model.fit(X_train, y_train) # Predict Class labels pred_labels_val = model.predict(X_val) pred_labels_test = model.predict(X_test) # F1 Score f1_val = metrics.f1_score(y_val, pred_labels_val) f1_test = metrics.f1_score(y_test, pred_labels_test)
但最终得到的评估指标(如F1 score)存在差异。
原因分析与解决办法
核心原因
fit()的初始化逻辑差异:即便设置了random_state=42,每次调用model.fit()时,SAGA求解器的内部状态(梯度缓存、学习率调整轨迹等)会被重置,而非延续上一次的状态。而单次fit()是连续完成所有迭代,内部状态连贯更新,两者的参数更新路径完全不同。- 收敛判断的实际差异:单次
fit()中,sklearn会在每轮后检查损失变化是否低于tol阈值,达到收敛就提前停止,但n_iter_记录的是总轮次。你用这个轮次强制跑满迭代训练,可能单次训练最后几轮已经停止更新参数,而迭代训练还在继续调整,导致参数结果偏离。 - 样本打乱的随机性差异:SAGA默认开启
shuffle=True,单次fit()仅在训练开始时打乱一次样本;而迭代训练每次调用fit()都会重新打乱样本,每轮的样本顺序不同,参数更新方向自然出现偏差。
解决方法
要让迭代训练和单次训练结果一致,核心是保留求解器的训练状态,而非每次fit()都重新初始化:
- 开启
warm_start=True:这是关键参数,开启后每次fit()会从上一次训练的参数继续更新,而非重置。修改后的迭代训练代码如下:
# 先获取常规训练的收敛轮次 base_model = LogisticRegression( random_state=42, C=.001, penalty='l1', max_iter=500, solver='saga') base_model.fit(X_train, y_train) epochs = base_model.n_iter_[0] # 初始化带warm_start的模型 model = LogisticRegression( random_state=42, C=.001, penalty='l1', max_iter=1, solver='saga', warm_start=True) # 保留训练状态 for epoch in range(epochs): model.fit(X_train, y_train) # 在这里记录每一轮的MSE y_pred_proba = model.predict_proba(X_train)[:, 1] mse = metrics.mean_squared_error(y_train, y_pred_proba) print(f"Epoch {epoch+1}, MSE: {mse}") # 后续评估逻辑不变 pred_labels_val = model.predict(X_val) pred_labels_test = model.predict(X_test) f1_val = metrics.f1_score(y_val, pred_labels_val) f1_test = metrics.f1_score(y_test, pred_labels_test)
- 固定样本顺序(可选):如果需要完全一致的样本遍历顺序,可设置
shuffle=False,但这可能降低模型收敛速度,需根据场景权衡。 - 手动加入收敛判断:在迭代训练中复刻sklearn的收敛逻辑(监测损失变化是否小于
tol),避免多跑不必要的迭代轮次。
内容的提问来源于stack exchange,提问作者neverreally
相关产品推荐
相关产品推荐

